Gallon's Note


  • 首页

  • 标签

  • 分类

  • 归档

  • 搜索

leetcode矩阵问题

发表于 2019-12-17 | 分类于 leetcode

54. 螺旋矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
private List<Integer> result;
//一圈一圈进行打印,当左上角元素在右下角元素的上方或者右方时结束
public List<Integer> spiralOrder(int[][] matrix) {
result = new ArrayList<>();
if(matrix.length==0||matrix[0].length==0){
return result;
}
int left_row = 0;
int left_col = 0;
int right_row = matrix.length-1;
int right_col = matrix[0].length-1;
while(left_row<=right_row&&left_col<=right_col){
helper(matrix,left_row++,left_col++,right_row--,right_col--);
}
return result;
}

/**打印一圈元素局灶性,这实际上是一个矩形,需要传递该矩形的左上角以及左下角坐标
* 当两个坐标同行,直接打印
* 当两个坐标同列,直接打印
* 两个坐标不同行也不同列:
*
*/
public void helper(int[][] matrix,int left_row,int left_col,int right_row,int right_col){
if(left_row==right_row){
for(int i= left_col;i<=right_col;i++){
result.add(matrix[left_row][i]);
}
}else if(left_col==right_col){
for(int i= left_row;i<=right_row;i++){
result.add(matrix[i][left_col]);
}
}else{
int curCol = left_col;
while(curCol!=right_col){
result.add(matrix[left_row][curCol]);
curCol++;
}
int curRow = left_row;
while(curRow!=right_row){
result.add(matrix[curRow][right_col]);
curRow++;
}

while(curCol!=left_col){
result.add(matrix[right_row][curCol]);
curCol--;
}
while(curRow!=left_row){
result.add(matrix[curRow][left_col]);
curRow--;
}
}
}

498. 对角线遍历

1576572207688

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 当a点走完最后一个点,可以结束循环
* 执行完一次对角线打印,坐标变换方式:
* b:先下,走到底了,再右
* a:先右,走到最右,再下
* */
private int arr_index= 0;
public int[] findDiagonalOrder(int[][] matrix) {

if(matrix.length==0||matrix[0].length==0){
return new int[0];
}
int[] result = new int[matrix.length*matrix[0].length];
arr_index = 0;
boolean form = false;
int row_lenth = matrix.length-1;
int col_length = matrix[0].length-1;
int a_row =0,b_row = 0,a_col=0,b_col=0;
//a到达最后一个节点
while(a_row<=row_lenth){
helper(matrix,a_row,a_col,b_row,b_col,form,result);
if(a_col<col_length){
a_col++;
}else{
a_row++;
}
if(b_row<row_lenth){
b_row++;
}else{
b_col++;
}
form=!form;
}
return result;

}
/**
* 打印一个对角,a为右上角元素,b为左下角元素
* 因为有左到右以及右到左两种方式交替,所以需要一个form参数
* 右到左:a_row>=b_row 赋值 移动到做下第一个点:a_row-- a_col--
* */
public void helper(int[][] matrix,int a_row,int a_col,int b_row,int b_col,boolean form,int[] result){
if(form){
while(a_row<=b_row&&a_col>=b_col){
result[arr_index++] = matrix[a_row++][a_col--];
}
}else{
while(a_row<=b_row&&a_col>=b_col){
result[arr_index++] = matrix[b_row--][b_col++];
}
}
}

leetcode栈和队列

发表于 2019-12-16 | 分类于 leetcode

数组实现栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//使用index来代表栈顶位置,index=0,栈为空,index = size,栈满
public class ArrStack {
private int[] arr;
private int index;
public ArrStack(int size){
arr = new int[size];
}
public void push(int x){
if(index==arr.length){
throw new ArrayIndexOutOfBoundsException("full");
}
arr[index++] = x;
}

public int pop(){
if(index==0){
throw new ArrayIndexOutOfBoundsException("empty");
}
return arr[--index];
}
public int peek(){
if(index==0){
throw new ArrayIndexOutOfBoundsException("empty");
}
return arr[index-1];
}
public boolean isEmpty(){
return index==0;
}
}

622.循环队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class MyCircularQueue {
/**
* 记录队头的front,记录队尾的wear,size表示队列大小
* front指向第一个元素 rear指向最后一个元素后一个位置
* 入队:arr[rear]=value,rear后移,size++,注意,因为要完成一个循环队列rear+1需要取模运算
* 出队:font后移取模运算,size--
* */
private int front = 0;
private int rear = 0;
private int size = 0;
private int[] arr;
/** Initialize your data structure here. Set the size of the queue to be k. */
public MyCircularQueue(int k) {
arr = new int[size];
}

/** Insert an element into the circular queue. Return true if the operation is successful. */
public boolean enQueue(int value) {
if(size==arr.length){
return false;
}
arr[rear] = value;
rear = (rear +1)%arr.length;
size++;
return true;
}

/** Delete an element from the circular queue. Return true if the operation is successful. */
public boolean deQueue() {
if(size==0){
return false;
}
front = (front++)%arr.length;
size--;
return true;
}

/** Get the front item from the queue. */
public int Front() {
if(size==0){
throw new RuntimeException("empty");
}
return arr[front];
}

/** Get the last item from the queue. */
public int Rear() {
if(size==0){
throw new RuntimeException("empty");
}
return arr[(rear-1)%arr.length];
}

/** Checks whether the circular queue is empty or not. */
public boolean isEmpty() {
return size==0;
}

/** Checks whether the circular queue is full or not. */
public boolean isFull() {
return size==arr.length;
}
}

155.最小栈

设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。

push(x) – 将元素 x 推入栈中。
pop() – 删除栈顶的元素。
top() – 获取栈顶元素。
getMin() – 检索栈中的最小元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Stack<Integer> data = new Stack<>();
Stack<Integer> min = new Stack<>();
public MinStack() {

}

/**入栈的时候除了data栈之外,还包括min栈,存储的每一个元素其相应的最小值
*
*/

public void push(int x) {
data.push(x);
if(min.isEmpty()){
min.push(x);
}else{
int minVal = min.peek();
if(x<minVal){
min.push(x);
}else{
min.push(minVal);
}
}

}

public void pop() {
if(!data.isEmpty()) {
data.pop();
min.pop();
}else{
throw new ArrayIndexOutOfBoundsException("empty");
}
}

public int top() {
int res = 0;
if(!data.isEmpty()) {
res = data.peek();
}else{
throw new ArrayIndexOutOfBoundsException("empty");
}
return res;
}

public int getMin() {
int res = 0;
if(!min.isEmpty()) {
res = min.peek();
}else{
throw new ArrayIndexOutOfBoundsException("empty");
}
return res;
}

255.用队列实现栈

队列是先进先出的,我们准备两个队列data,helper,data存放入队的数据,pop的时候将前n-1个元素从data出队到helper中,这样data中就只剩下一个最先进队的元素,即可以达到先进后出的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
private Queue<Integer> data = new LinkedList<>();
private Queue<Integer> helper = new LinkedList<>();
private int top;
public MyStack() {

}

/** 放入到data队列中 */
public void push(int x) {
data.add(x);
}

/** pop元素的时候,将前n-1个元素放入helper中,只留下最后一个元素,这样,就可以达到栈的目的
* */
public int pop() {
while(data.size()>1){
helper.add(data.remove());
}
int res = data.remove();
swap();
return res;
}
public void swap(){
Queue<Integer> tmp = this.data;
this.data = this.helper;
this.helper = tmp;
}
/** Get the top element. */
public int top() {
while(data.size()>1){
helper.add(data.poll());
}
int res = data.poll();
helper.add(res);
swap();
return res;
}

/** Returns whether the stack is empty. */
public boolean empty() {
return data.size()==0;
}

232.用栈实现队列

因为栈是先进后出,因此元素先进去一个栈push,在从该栈push出栈到另一个栈pop,这样,我们从pop中取出元素就可以达到先进先出的目的。

需要注意的是,元素从push到pop,需要满足两个条件:需要一次性将push栈元素出栈完;当pop中有元素时,不可以入栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Stack<Integer> sta_pop = new Stack<>();
/** Initialize your data structure here. */
public MyQueue() {

}

/** Push element x to the back of queue. */
public void push(int x) {
pushtoPop();
sta_push.push(x);
}

/** Removes the element from in front of queue and returns that element. */
public int pop() {
pushtoPop();
return sta_pop.pop();
}

/** Get the front element. */
public int peek() {
pushtoPop();
return sta_pop.peek();
}
//将元素从push推到pop
//1.一次性推完push中元素 2.pop中为空
public void pushtoPop(){
if(sta_pop.isEmpty()) {
while (!sta_push.isEmpty()) {
sta_pop.push(sta_push.pop());
}
}
}

/** Returns whether the queue is empty. */
public boolean empty() {
return sta_pop.isEmpty()&&sta_push.isEmpty();
}

leetcode搜索

发表于 2019-12-11 | 分类于 leetcode

BFS:需要记录两层的节点,空间复杂度是指数级别的。O(2^h),queue

DFS:空间复杂度为树的深度。O(h),stack

BFS空间复杂度比较高,但是可以搜索最小性质。

空间复杂度 数据结构
DFS O(h),h为树的高度 stack
BFS O(2^h),指数级别的 queue 可以求出最短路的问题(边的权重是1)

1576072859183

111.二叉树最小深度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int minDepth(TreeNode root) {
return helper(root);
}

/**
* 左右节点均不为空使,返回min(left,right)+1
* 左右节点只有一个为空(left==null)时,需要返回right+1,而不是返回min(lef,right)+1
* */
public int helper(TreeNode root){
if(root==null){
return 0;
}
int left = helper(root.left);
int right = helper(root.right);
if(root.left==null||root.right==null){
return left+right+1;
}
return Math.min(left,right)+1;
}

279.完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

leetcode树

发表于 2019-12-07 | 分类于 leetcode

a搜索二叉树:左子树小于根节点,右子树大于根节点。

平衡树:左右子树高度差不大于1

一般解决二叉树的问题采用递归以及递归转非递归的方法。

98.验证二叉搜索树?

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
左子树的所有点应该在(minVal,root),右子树在(root,maxVal)
*/
public boolean isValidBST(TreeNode root) {
return helper(root,Long.MIN_VALUE,Long.MAX_VALUE);
}

public boolean helper(TreeNode root,long minVal,long maxVal){
if(root==null){
return true;
}
if(root.val<=minVal||root.val>=maxVal){
return false;
}
return helper(root.left,minVal,root.val)&&helper(root.right,root.val,maxVal);
}

还有一种做法:中序遍历结果是递增的,即为二叉搜索树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public boolean isValidBST(TreeNode root) {
boolean isRoot = true;
if(root==null){
return true;
}
Stack<TreeNode> stack = new Stack<>();
int last = Integer.MIN_VALUE;
while(!stack.isEmpty()||root!=null){
while(root!=null){
stack.push(root);
root = root.left;
}
if(!stack.isEmpty()){
TreeNode node = stack.pop();
if(isRoot){
last = node.val;
isRoot = false;
}else if(node.val>last){
last = node.val;
}else{
return false;
}
//dayin
root = node.right;
}
}
return true;
}

144.前序非递归遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while(!stack.isEmpty()||root!=null){
while(root!=null){
stack.push(root);
result.add(root.val);
root = root.left;
}
if(!stack.isEmpty()) {
TreeNode node = stack.pop();
root = node.right;
}
}
return result;
}

94.中序非递归遍历

1.将整棵树的左子树压入栈中

2.每次取出栈顶元素,如果他有右子树,则将右子树压入栈中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 将root的最左的元素全部压入栈,当root==null,表示压到底了,最左侧已经没有元素了,此时,可以将栈顶元素取出
* 并开始对右子树做同样操作
* */
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while(!stack.isEmpty()||root!=null){
while (root!=null){
stack.push(root);
root = root.left;
}
if(!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
root = node.right;
}
}
return result;
}

144.后序非递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 前序根右左,反转为左右根即可
* */
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while(!stack.isEmpty()||root!=null){
while(root!=null){
result.add(0,root.val);
stack.push(root);
root = root.right;
}
if(!stack.isEmpty()){
TreeNode node = stack.pop();
root = node.left;
}
}
return result;
}

101.对称二叉树?

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

1
2
3
4
5
    1
/ \
2 2
/ \ / \
3 4 4 3

递归做法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 两个根节点值相等
* 左边的左子树和右边的右子树相等
* 左边的右子树和右边的左子树相等
* */
public boolean isSymmetric(TreeNode root) {
//一般二叉树的题目首先判断是否null
if(root==null){
return true;
}
return helper(root,root);
}

public boolean helper(TreeNode p,TreeNode q){
// 两个节点同时为空,返回true 只有一个节点为空,返回false
if(p==null||q==null){
return p==null&&q==null;
}
return p.val==q.val&&helper(p.left,q.right)&&helper(p.right,q.left);
}

非递归方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
*对称树的话,对于根节点的左右子树分别进行左根右以及右左根应该是相同的
* */
public boolean isSymmetric(TreeNode root) {
if(root==null){
return true;
}
return inOrder(root.left,root.right);
}

public boolean inOrder(TreeNode left ,TreeNode right){
Stack<TreeNode> s_left = new Stack<>();
Stack<TreeNode> s_right = new Stack<>();
while (left!=null||right!=null||!s_left.isEmpty()||!s_right.isEmpty()){
while(left!=null&&right!=null){
s_left.push(left);
s_right.push(right);
left = left.left;
right = right.right;
}
//假如left与right不等,表明左子树个数不一样,必定不同
if(left!=right){
return false;
}

if(s_left!=null&&s_right!=null){
TreeNode leftnode = s_left.pop();
TreeNode rightnode = s_right.pop();
if(leftnode.val!=rightnode.val){
return false;
}
left = leftnode.right;
right = rightnode.left;
}
}
return true;
}

105.从前序和中序遍历序列构造二叉树

递归的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public TreeNode buildTree(int[] preorder, int[] midorder) {
//设置一个map存储索引位置,查询方便
HashMap<Integer,Integer> midorder_index = new HashMap<>();
for(int i=0;i<midorder.length;i++){
midorder_index.put(midorder[i],i);
}
return helper(preorder,midorder,0,preorder.length-1,0,preorder.length-1,midorder_index);
}
/**
* preorder[p_start]是根节点,这样就可以找到preorder[p_start]在中序遍历中的位置,这样,就可以获得左子树以及右子树的范围,接着递归来做
* */
public TreeNode helper(int[] preorder , int[] midorder, int p_start, int p_end, int m_start, int m_end, Map<Integer,Integer> midorder_index){
//此时没有元素了,直接返回null
if(p_start>p_end){
return null;
}
//获取根节点
int root_val = preorder[p_start];
TreeNode root = new TreeNode(root_val);
int index = midorder_index.get(root_val);
//左子树的长度
int length = index - m_start;
root.left = helper(preorder,midorder,p_start+1,p_start+length,m_start,index-1,midorder_index);
root.right = helper(preorder,midorder,p_start+length+1,p_end,index+1,m_end,midorder_index);
return root;

}

106. 从中序与后序遍历序列构造二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public TreeNode buildTree(int[] inorder, int[] postorder) {
Map<Integer,Integer> map = new HashMap<>();
for(int i=0;i<inorder.length;i++){
map.put(inorder[i],i);
}
return helper3(inorder,postorder,0,inorder.length-1,0,inorder.length-1,map);
}
public TreeNode helper3(int[] inorder, int[] postorder,int i_start,int i_end,int p_start,int p_end,Map<Integer,Integer> map){
if(i_start>i_end){
return null;
}
if(i_start==i_end){
return new TreeNode(postorder[p_start]);
}
int root_val = postorder[p_end];
int index = map.get(root_val);
int length = index - i_start;
TreeNode root = new TreeNode(root_val);
root.left = helper3(inorder,postorder,i_start,index-1,p_start,p_start+length-1,map);
root.right = helper3(inorder,postorder,index+1,i_end,p_start+length,p_end-1,map);
return root;
}

889. 根据前序和后序遍历构造二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public TreeNode constructFromPrePost(int[] pre, int[] post) {
Map<Integer,Integer> map = new HashMap<>();
for(int i=0;i<pre.length;i++){
map.put(post[i],i);
}
return helper2(pre,post,0,pre.length-1,0,pre.length-1,map);
}

public TreeNode helper2(int[] pre, int[] post, int pre_start, int pre_end, int post_start, int post_end, Map<Integer,Integer> map){
if(pre_start>pre_end){
return null;
}
if(pre_start==pre_end){
return new TreeNode(pre[pre_start]);
}

int root_val = pre[pre_start];
int left_val = pre[pre_start+1];
int index = map.get(left_val);
TreeNode root = new TreeNode(root_val);
int length = index-post_start;
root.left = helper2(pre,post,pre_start+1,pre_start+1+length,post_start,index,map);
root.right = helper2(pre,post,pre_start+1+length+1,pre_end,index+1,post_end-1,map);
return root;
}

102.二叉树层次遍历?

通过队列来解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if(root==null){
return result;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.add(root);
while(queue.size()>0){
List<Integer> vals = new ArrayList<>();
int size = queue.size();
for(int i=0;i<size;i++){
TreeNode node = queue.remove();
vals.add(node.val);
if(node.left!=null) {
queue.add(node.left);
}
if(node.right!=null) {
queue.add(node.right);
}
}
result.add(vals);
}
return result;
}

236.二叉树的最近公共祖先?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 后序遍历二叉树。
* 左子树返回不为空,右子树返回不为空,则该node必定是公共祖先
* */
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//当前节点==p或者==q,那么该点必定是祖先
if(root==null||root==p||root==q){
return root;
}
//递归左右子树
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
//假如左右子树返回均不为空,那么当前节点就是公共祖先
if(left!=null&&right!=null){
return root;
}
//只有一个子树返回的不是空
return left!=null?left:right;
}

543.二叉树的直径

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过根结点。

示例 :
给定二叉树

1
2
3
4
5
    1
/ \
2 3
/ \
4 5

返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 某一个节点的直径=左子树深度+右子树深度
* 树的深度 = max(左子树深度,右子树深度)+1
* 因为要求任意两个节点的直径,所以还需要一个ans记录当前直径的最大值
* */
int ans = 0;
public int diameterOfBinaryTree(TreeNode root) {
helper(root);
return ans;
}
//helper返回的是树的深度,而直径最大值采用ans来记录
public int helper(TreeNode root){
if(root==null){
return 0;
}
//左右子树的深度
int left = helper(root.left);
int right = helper(root.right);
//计算该点的直径,并与最大直径比较
int zhijing = left+right;
ans = zhijing>ans?zhijing:ans;
//返回树的深度
return (left>right?left:right)+1;
}

124.二叉树中最大路径和

给定一个非空二叉树,返回其最大路径和。

本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。

示例 2:

输入: [-10,9,20,null,null,15,7]

-10
/
9 20
/
15 7

输出: 42

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int res=Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
return helper(root);
}
public int helper(TreeNode root){
if(root==null){
return 0;
}
int left = helper(root.left);
int right = helper(root.right);
int root_res = left+right+root.val;
res = Math.max(root_res,res);
return Math.max(0,Math.max(left,right)+root.val);
}

173.二叉搜索树迭代器

可以用中序遍历来解决,但是要求空间复杂度为O(h),h为树的深度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class BSTIterator {
Stack<TreeNode> stack = new Stack<>();
public BSTIterator(TreeNode root) {
while(root!=null){
stack.push(root);
root = root.left;
}
}

/** @return the next smallest number */
public int next() {
TreeNode node = stack.pop();
int res = node.val;
node = node.right;
while(node!=null){
stack.push(node);
node = node.left;
}
return res;
}

/** @return whether we have a next smallest number */
public boolean hasNext() {
return !stack.isEmpty();
}
}

285.后继节点问题

1576680169342

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 中序遍历:左根右
* 所以,假如该节点有右子树,那么后继节点是右子树的最左一个节点
* 假如该节点没有右子树,那么他一定是某一个节点p的左子树的最右节点,因此该节点后继节点就是p
* */
public TreeNode getSuccessorNode(TreeNode node){
if(node==null){
return null;
}
if(node.right!=null){
return getRightMostLeft(node.right);
}else{
TreeNode parent = node.parent;
while(parent!=null&&parent.left!=node){
node = node.parent;
parent = node.parent;
}
return parent;
}
}

public TreeNode getRightMostLeft(TreeNode node){
while(node.left!=null){
node = node.left;
}
return node;
}

297. 二叉树的序列化与反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public String serialize(TreeNode root) {
if(root==null){
return "#!";
}
String res = root.val+"!";
res += serialize(root.left);
res+= serialize(root.right);
return res;
}

// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] values = data.split("!");
Queue<String> queue = new LinkedList<>();
for(String s:values){
queue.add(s);
}
return recoverFromPre(queue);
}

public TreeNode recoverFromPre(Queue<String> queue){
String value = queue.poll();
if(value.equals("#")){
return null;
}else{
TreeNode root = new TreeNode(Integer.parseInt(value));
root.left = recoverFromPre(queue);
root.right = recoverFromPre(queue);
return root;
}
}

层次遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public String serialize(TreeNode root) {
if(root==null){
return "#!";
}
StringBuilder res= new StringBuilder(root.val+"!");
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(queue.size()>0){
TreeNode node = queue.poll();
if(node.left!=null){
queue.add(node.left);
res.append(node.left.val+"!");
}else{
res.append("#!");
}
if(node.right!=null){
queue.add(node.right);
res.append(node.right.val+"!");
}else{
res.append("#!");
}
}
return res.toString();
}

// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] values = data.split("!");
int index = 0;
if(values[0].equals("#")){
return null;
}
Queue<TreeNode> queue = new LinkedList<>();
TreeNode root = new TreeNode(Integer.parseInt(values[index++]));
queue.add(root);
while(queue.size()>0){
TreeNode node = queue.poll();
String s_left = values[index++];
String s_right = values[index++];
if(s_left.equals("#")){
node.left = null;
}else{
node.left = new TreeNode(Integer.parseInt(s_left));
queue.add(node.left);
}
if(s_right.equals("#")){
node.right = null;
}else{
node.right = new TreeNode(Integer.parseInt(s_right));
queue.add(node.right);
}
}
return root;
}

110. 平衡二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public boolean isBalanced(TreeNode root) {
isBalance = true;
healperBalance(root);
return isBalance;
}

private boolean isBalance = true;
//返回树的高度
public int healperBalance(TreeNode root){
if(root==null){
return 0;
}
int left = healperBalance(root.left);
int right = healperBalance(root.right);
if(Math.abs(left-right)>1){
isBalance = false;
}
return Math.max(left,right)+1;
}

958. 完全二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 层序遍历,设置一个停止标志,遇到空节点,停止标志为真,如果停止标志为真,再遍历遇到非空节点,则非完全二叉树。
* */
public boolean isCompleteTree(TreeNode root) {
if(root==null){
return true;
}
//当遇到第一个空节点时,将firstFull设置为true,假如是完全二叉树,后面所有节点都应该是空节点,不会再遇到非空节点
boolean firstNull = false;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(queue.size()>0) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.add(node.left);
//假如firstNull==true,该非空节点前面有一个空节点,必定不是完全二叉树
if (firstNull) {
return false;
}
} else {
//第一个非空节点,记录标志
if (!firstNull) {
firstNull = true;
}
}
if (node.right != null) {
queue.add(node.right);
if (firstNull) {
return false;
}
} else {
if (!firstNull) {
firstNull = true;
}
}

}
return true;
}

222. 完全二叉树的节点个数?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 寻找root右子树的最左节点,假如该节点位于最后一层,那么root的左子树必定是满二叉树,可以计算出节点个数,对于右子树,继续递归
* 假如该节点位于倒数第二层,那么右子树是满二叉树,同理可计算
* */
private int height = 0;
public int countNodes(TreeNode root) {
if(root==null){
return 0;
}
height = getHeight(root,1);
return helpCount(root,1);
}
public int getHeight(TreeNode root,int level){
while(root!=null){
level++;
root = root.left;
}
return level-1;
}
public int helpCount(TreeNode root,int level){
if(level==height){
return 1;
}
//获取右子树最左节点的层数
int leftMostheight = getHeight(root.right,level+1);
//最左节点位于最后一层,左子树是满二叉树
if(leftMostheight==height){
return (1<<(height-level))+helpCount(root.right,level+1);
}else{
return (1<<(height-level-1))+helpCount(root.left,level+1);
}
}
  • 不判断root==null
  • ||以及&&的问题

leetcode数据结构

发表于 2019-12-06 | 分类于 leetcode

单调栈

单调栈主要解决以下几个问题:

  • 比当前元素更大的下一个元素
  • 比当前元素更大的前一个元素
  • 比当前元素更小的下一个元素
  • 比当前元素更小的前一个元素

寻找比当前元素更大的下一个元素或者下一个元素,那么应该使用栈底到栈顶由大到小的单调栈。当某个元素大于栈顶元素,将栈顶元素弹出,而该元素就是栈顶元素的最右侧的第一个大于它的元素。最左侧的最大元素,那么,某个元素在栈中的下一个元素就是大于它的第一个元素。

寻找比当前元素更小的下一个元素或者前一个元素,则使用栈底到栈顶由小到大的单调栈。

496.下一个更大元素

给定两个没有重复元素的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。

nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出-1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* 因为nums1是nums2的子集,所以只需要求出nums2中的每一个元素的下一个更大元素即可,存储在map中,使用单调递增来解决
* 遍历nums2,当栈顶元素比他小的时候,该元素是没有用的,将其踢出栈,并入栈
* 当栈顶元素大于该元素的时候,保留栈顶元素,入栈
* */
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
HashMap<Integer,Integer> map = new HashMap<>();
Stack<Integer> stack = new Stack<>();
for(int i=nums2.length-1;i>=0;i--){
while(!stack.isEmpty()&&stack.peek()<=nums2[i]){
stack.pop();
}
map.put(nums2[i],stack.isEmpty()?-1:stack.peek());
stack.push(nums2[i]);
}
int[] result = new int[nums1.length];
for(int i=0;i<nums1.length;i++){
result[i] = map.get(nums1[i]);
}
return result;
}




/**
* 因为要求更大元素,建立一个从栈底到栈顶由大到小的栈.
* 当某个元素大于栈顶元素,将栈顶元素弹出,而该元素就是栈顶元素的最右侧的第一个大于它的元素。
* 而要是求最左侧的最大元素,那么,某个元素在栈中的下一个元素就是大于它的第一个元素。
* */
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
Map<Integer,Integer> map = new HashMap<>();
Stack<Integer> statck = new Stack<>();
for(int i=0;i<nums2.length;i++){
while(!statck.isEmpty()&&nums2[statck.peek()]<nums2[i]){
int index = statck.pop();
map.put(nums2[index],nums2[i]);
}
statck.push(i);
}
while(!statck.isEmpty()){
map.put(nums2[statck.pop()],-1);
}
int[] res = new int[nums1.length];
for(int i=0;i<nums1.length;i++){
res[i] = map.get(nums1[i]);
}
return res;
}

503.下一个更大元素2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),
* 输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,
* 这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。
* 如果不存在,则输出 -1。
*
* 关于环型数组的问题,构建一个环型数组可以通过取余运算来解决
* 例如,[1,3,4,2],我们将其当作[1,3,4,2,1,3,4,2]来看待,遍历这个2n长度的数组
* 其他的,与之前的求下一个最大元素类似,使用单调栈来解决
* */
public int[] nextGreaterElements(int[] nums) {
Stack<Integer> stack = new Stack<>();
int[] result = new int[nums.length];
int length = nums.length;
for(int i = 2*nums.length-1;i>=0;i--){
while(!stack.isEmpty()&&stack.peek()<=nums[i%length]){
stack.pop();
}
result[i%length] = stack.isEmpty()?-1:stack.peek();
stack.push(nums[i%length]);
}
return result;
}

public int[] nextGreaterElements(int[] nums) {
int n = nums.length;
int[] res = new int[n];
Stack<Integer> stack = new Stack<>();
for(int i = 0;i<nums.length*2;i++){
while(!stack.isEmpty()&&nums[i%n]>nums[stack.peek()%n]){
int index = stack.pop()%n;
res[index%n] = nums[i%n];
}
stack.push(i);
}
while(!stack.isEmpty()){
int index = stack.pop();
if(index<nums.length){
res[index] = -1;
}
}
return res;
}

556.下一个更大元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
 /**
* 给定一个32位正整数 n,你需要找到最小的32位整数,其与 n 中存在的位数完全相同,
* 并且其值大于n。如果不存在这样的32位整数,则返回-1。
*1.寻找第一个不是单调减的元素i,假如没有,返回-1
* 2.寻找大于i的最小元素
* 3.交换两个元素
* 4.将i之后的元素反置
* */
public int nextGreaterElement(int n) {
char[] a = (""+n).toCharArray();
if(a.length<2){
return -1;
}
//从尾部寻找第一个不是递减的元素
int index = a.length-2;
while(index>=0&&a[index+1]<=a[index]){
index--;
}
if(index<0){
return -1;
}
int j = a.length-1;
//寻找大于a[index]的
while(j>index&&a[index]>=a[j]){
j--;
}
swap(a,index,j);
reverse(a,index+1,a.length-1);
//注意处理异常,可能会溢出int范围
try {
return Integer.parseInt(new String(a));
}catch(Exception e){
return -1;
}
}
public void swap(char[] a,int i,int j){
char tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}

public void reverse(char[] a,int i,int j){
while(i<j){
swap(a,i,j);
i++;
j--;
}
}

739.每日温度

下一个更大元素问题,单调递增栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 每日温度
* 根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。
* 如果之后都不会升高,请在该位置用 0 来代替。
*
*单调递增栈来解决
* */
public int[] dailyTemperatures(int[] T) {
Stack<Integer> stack = new Stack<>();
int[] res = new int[T.length];
for(int i=0;i<T.length;i++){
while(!stack.isEmpty()&&T[i]>T[stack.peek()]){
int index = stack.pop();
res[index] = i-index;
}
stack.push(i);
}
while(!stack.isEmpty()){
int index = stack.pop();
res[index] = 0;
}
return res;
}

901.股票价格跨度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。
* 其实就是求最近的大于他的元素
* */
public class StockSpanner {
Stack<Integer> prices = new Stack<>();
Stack<Integer> weights = new Stack<>();
public StockSpanner() {

}

public int next(int price) {
int weight = 1;
while(!prices.isEmpty()&&prices.peek()<=price){
prices.pop();
weight+=weights.pop();
}
prices.push(price);
weights.push(weight);
return weight;
}
}

题解

84. 柱状图中最大的矩形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//本质就是找到某个元素左侧第一个小于它的元素,以及右侧小于它的第一个元素
public int largestRectangleArea(int[] nums) {
if(nums.length==0){
return 0;
}
Stack<Integer> stack = new Stack<>();
int max = 0;
for(int i =0;i<nums.length;i++){
while(!stack.isEmpty()&&nums[i]<nums[stack.peek()]){
int ele = stack.pop();
int after_index = i;
int before_index = stack.isEmpty()?-1:stack.peek();
max = Math.max(max,(after_index-before_index-1)*nums[ele]);
}
stack.push(i);
}

while(!stack.isEmpty()){
int after_index = nums.length;
int ele = stack.pop();
int before_index = stack.isEmpty()?-1:stack.peek();
max = Math.max(max,(after_index-before_index-1)*nums[ele]);
}
return max;
}

85. 最大矩形

本题与上一题类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* 单调栈的应用
* 对于某一个数组[3,2,4,2],求他形成的最大的最大矩形面积
* 例如,对3,求其两侧的第一个小于它的元素,这两者之间的距离即是可以形成的矩形
* */
public int maximalRectangle(char[][] matrix) {
int max = 0;
if(matrix.length==0||matrix[0].length==0){
return max;
}
int[] helper = new int[matrix[0].length];
for(int i=0;i<matrix.length;i++){
for(int j =0;j<matrix[0].length;j++){
if(String.valueOf(matrix[i][j]).equals("0")){
helper[j] = 0;
}else{
helper[j]++;
}
}
max = Math.max(max,generateMatrixArea(helper));
}
return max;
}
//本质就是找到某个元素左侧第一个小于它的元素,以及右侧小于它的第一个元素
public int generateMatrixArea(int[] nums){
Stack<Integer> stack = new Stack<>();
int max = Integer.MIN_VALUE;
for(int i =0;i<nums.length;i++){
while(!stack.isEmpty()&&nums[i]<nums[stack.peek()]){
int ele = stack.pop();
int after_index = i;
int before_index = stack.isEmpty()?-1:stack.peek();
max = Math.max(max,(after_index-before_index-1)*nums[ele]);
}
stack.push(i);
}

while(!stack.isEmpty()){
int after_index = nums.length;
int ele = stack.pop();
int before_index = stack.isEmpty()?-1:stack.peek();
max = Math.max(max,(after_index-before_index-1)*nums[ele]);
}
return max;
}

42. 接雨水

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//使用单调递减栈,假如某个元素大于栈顶元素,因为元素栈底到栈顶是由大到小的,所以必定可以形成雨水区域
public int trap(int[] height) {
if(height.length<3){
return 0;
}
Stack<Integer> stack = new Stack<>();
int res = 0;
for(int i=0;i<height.length;i++){
while(!stack.isEmpty()&&height[stack.peek()]<height[i]){
int top = stack.pop();
if(stack.isEmpty()){
break;
}
//计算雨水区域大小,注意是i-stack.peek()-1,不是i-top
res+=(i-stack.peek()-1)*(Math.min(height[i],height[stack.peek()])-height[top]);
}
stack.push(i);
}
return res;
}

题解

单调队列

滑动窗口最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 /**
* 双端队列
* 队头永远存储最大值,双端队列是单调递减的。
* 入队的时候,假如<队尾元素,直接入队,假如>=队尾元素,那么将队尾元素挤掉,直到<队尾元素
* 还需要注意的是,需要对队头元素是否过期进行判断:i-w==queue.peekFirst()
* */
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length<k||nums.length<1){
return new int[0];
}
//队列中存储的是下标
LinkedList<Integer> queue = new LinkedList<>();
int[] res = new int[nums.length-k+1];
int index = 0;
for(int i=0;i<nums.length;i++){
//假如>=队尾元素,那么将队尾元素挤掉,直到<队尾元素
while(!queue.isEmpty()&&nums[i]>=nums[queue.peekLast()]){
queue.pollLast();
}
//元素入队
queue.addLast(i);
//过期判定
if(i-k==queue.peekFirst()){
queue.pollFirst();
}
//窗口中至少有k个元素,求取最值
if(i>=k-1){
res[index++] = nums[queue.peekFirst()];
}
}
return res;
}

滑动窗口变形

这里写图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 准备两个单调队列,一个单调递减,存储的是最大值,一个单调递增,存储的是最小值
* 假如[L,R]区间内满足max-min<=target,那么[L,L..R]中也必定满足;假如[L,R]区间内不满足max-min<=target,那么[L,R.R+1...arr.length]中也必定不满足;
* */
public int SubArray(int[] nums,int target){
//单调减队列,队头存储的是最大值
LinkedList<Integer> qmax = new LinkedList<>();
//单调增队列,队头存储的是最小值
LinkedList<Integer> qmin = new LinkedList<>();
int left = 0;
int right = 0;
int res = 0;
while(left<nums.length){
//构建单调队列,假如nums[qmax.peekFirst()]-nums[qmin.peekFirst()]>target,表明以left开始,只有[left,left.....right)这些个子数组是符合条件的
while(right<nums.length){
while(qmax.size()>0&&nums[qmax.peekLast()]<=nums[right]){
qmax.pollLast();
}
qmax.push(right);
while(qmin.size()>0&&nums[qmin.peekLast()]>=nums[right]){
qmin.pollLast();
}
qmin.push(right);
if(nums[qmax.peekFirst()]-nums[qmin.peekFirst()]>target){
break;
}
right++;
}

//过期条件,因为需要将left右移,所以,下标为left的队列元素应该删除
if(qmax.peekFirst()==left){
qmax.pollFirst();
}
if(qmin.peekFirst()==left){
qmin.pollFirst();
}
res+=right-left;
left++;
}
return res;
}

滑动窗口处理子串的问题

关于处理最小字串,最长字串等问题,可以使用滑动窗口来解决。

1、我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引闭区间 [left, right] 称为一个「窗口」。

2、我们先不断地增加 right 指针扩大窗口 [left, right],直到窗口中的字符串符合要求(包含了 T 中的所有字符)。

3、此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。

4、重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。

这个思路其实也不难,第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
string s, t;
// 在 s 中寻找 t 的「最小覆盖子串」
int left = 0, right = 0;
string res = s;

while(right < s.size()) {
window.add(s[right++]);
// 如果符合要求,移动 left 缩小窗口
while (window 符合要求) {
// 如果这个窗口的子串更短,则更新 res
res = minLen(res, window);
window.remove(s[left++]);
}
}
return res;

76. 最小覆盖子串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
*需要准备两个指针,left以及right,将right不断右移,直到[left...right]包含了t,那么这是一个满足条件的子数组
* 但是,还有可能更短,将left右移动,直到[left...right]依旧包含t,当[left...right]不包含t的时候left移动停止
* 再将right右移,重复以上过程
* */
public String minWindow(String s, String t) {
if(s==null||t==null||s.length()<t.length()){
return "";
}
//存储欠的字符的个数,例如t="abc",那么初始应该前a:1,b:1,c:1
int[] map = new int[256];
for(int i=0;i<t.length();i++){
map[t.charAt(i)]++;
}
//还差多少个字符串才能包含t,初始为t的长度,当match==0时,表明已经包含了t
int match = t.length();
int left = 0;
int right = 0;
int min = Integer.MAX_VALUE;
String res="";
while(right<s.length()){
map[s.charAt(right)]--;
//假如某个字符的map>=0,match--
if(map[s.charAt(right)]>=0){
match--;
}
//假如match==0,表明找到了一个子数组;但有可能该子数组并不是最短的,因此,应该将left左移,假如移动过程中有一个字符欠元素了,left停止移动
if(match==0){
while(map[s.charAt(left)]<0){
map[s.charAt(left++)]++;
}
if(min>right-left+1){
min = right-left+1;
//beginIndex - 开始处的索引(包括)endindex 结尾处索引(不包括)
res = s.substring(left,right+1);
}
match++;
map[s.charAt(left++)]++;
}
right++;
}
return res;
}

3. 无重复字符的最长子串?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public int lengthOfLongestSubstring(String s) {
int left = 0;
int right = 0;
int max = 0;
//记录字符出现的次数
int[] map = new int[256];
while(right<s.length()){
char c1 = s.charAt(right);
map[s.charAt(right++)]++;
while(map[c1]>1){
map[s.charAt(left++)]--;
}
max = Math.max(max,right-left);
}
return max;
}

跳表

多级索引+链表。空间复杂度O(n),查找以及插入时间复杂度O(logn)。

对于链表的查找,常见的形式是O(n)的复杂度,需要遍历链表。

img

因此,可以抽取索引来解决。这样的话,时间复杂度就可以缩短一半。

img

skiplist正是受这种多层链表的想法的启发而设计出来的。

他会为每一个节点随机生成一个层数。

img

  • 跳表实际上是通过多级索引来达到一个链表的二分查找的目的。
  • 每个元素level是随机生成的。
  • 最底层包含所有元素。
  • 跳表查询、插入、删除的时间复杂度为O(log n)
  • 每个元素内部是一个list,例如,7就是一个由4个元素组成的list。

查找数据

查找时间复杂度是O(logn)。

从最高层开始,一层层遍历直到原始链表。

img

最高层索引:从head开始,head下一个元素未7,所以后移。7下一个未null,所以位于7,null之间,下沉。

二层索引:从7开始,7下一个元素是37,23位于7,37之间。下沉。

一级索引:从7开始,7下一个元素19,23不位于,向右移动,19下一个元素37,23位于。下沉。

原始链表:从19开始,19下一个元素是22,23不位于,向右移动,22下一个元素26,找到23位置。

插入数据

当插入数据的时候,根据该点生成的层数level,从level层开始,逐层开始插入。跟查找整个过程相似,只不过添加了一个插入元素的过程。

插入数据6,level=3。

插入数据且维护跳表图示1.jpeg

三级索引,1<6<13,所以往下移动。

插入数据且维护跳表图示2.jpeg

二级索引,6位于1,7之间,所以,直接在1,7之间插入7。

插入数据且维护跳表图示3.jpeg

一级索引,6不在1,4之间,所以向后移动,6位于4,7之间,所以插入。

插入数据且维护跳表图示4.jpeg

原始链表,6不位于4,5之间,向后移动,6位于5,7之间,直接插入。

删除数据

与查找,插入数据类似。时间复杂度也是O(logn)。

删除数据.jpeg

跳表

LRU和LFU

LRU:当容量满的时候,删除最久未使用的元素。

LFU:当容量满的时候,删除使用次数最少的,假如使用次数最少有多个元素,那么删除最久未使用的元素。

LRU

146.设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/**
* 需要准备一个map,一个双向链表,双向链表来解决访问记录的问题 .hashMap来存储数据。
* 双向链表来存储数据访问的先后顺序,头节点代表的就是最久未访问的数据
* 双向链表中存储的节点包含key以及value
* 添加元素时,需要将node插入到双向链表以及map中,而且,假如插入的key以及包含了,那么代表该点被访问了,需要将其移动到双向链表尾部;当添加元素大于容量,需要将双向链表头部元素以及map中相应元素删除
* 当访问某一个元素时,需要将该元素移动到链表尾部
* */
public class LRUCache {
//双链表中的数据节点,存储的是key
public class Node{
public int key;
public int value;
public Node pre;
public Node next;
Node(int key,int value){
this.key = key;
this.value = value;
}
}
//定义双链表
public class DoubleList{
private Node head;
private Node tail;
public DoubleList(){
this.head=null;
this.tail=null;
}
public void addNode(Node node){
if(node==null){
return ;
}
if(this.head==null){
head = node;
tail = node;
}else{
tail.next = node;
node.pre = tail;
tail = node;
}
}

//将某个节点移动到尾部,即将访问过的节点移动到尾部
public void moveToTail(Node node){
if(tail==node){
return;
}
if(head==node) {
head.next.pre = null;
head = head.next;
}else{
node.pre.next = node.next;
node.next.pre = node.pre;
}
tail.next = node;
node.pre = tail;
tail = node;
node.next = null;
}
//删除头节点
public Node removeHead(){
if(head==null){
return null;
}
Node tmp = head;
if(head==tail){
head=null;
tail=null;
}else {
head.next.pre = null;
head = head.next;
tmp.next = null;
}
return tmp;
}
}
Map<Integer,Node> map = new HashMap<>();
DoubleList list = new DoubleList();
private int capicity;
public LRUCache(int capacity) {
this.capicity = capacity;
}

public int get(int key) {
//map中含有该元素,直接获取value,并将节点移动至尾部
if(map.containsKey(key)){
list.moveToTail(map.get(key));
return map.get(key).value;
}else{
return -1;
}
}

public void put(int key, int value) {
//map中包含该元素,那么更新value,并将list中节点移动到队尾
if(map.containsKey(key)){
map.get(key).value = value;
list.moveToTail(map.get(key));
}else{
//map中没有该元素,新建节点直接插入map,并且插入到list尾部,假如超过了容量,需要list头部元素删除,并且删除map中相应元素
Node node = new Node(key,value);
map.put(key,node);
list.addNode(node);
if(map.size()==size+1){
removeHead();
}
}
}

//删除list头部元素以及map中相应元素
public void removeHead(){
Node node = list.deleteHeadNode();
map.remove(node.key);
}
}

LFU

当容量满的时候,删除的是访问次数最少的,假如有多个访问次数最少的节点,删除最久未访问的。

多个桶组成,每个桶代表的是访问次数的数值,桶内存储的是相同访问次数的所有节点,采用头插法,所以尾节点是该桶内最久未访问的节点。

各个桶之间也是使用双向链表来进行存储。

因此,需要定义一个Node:key,value,times(访问次数),pre,next.

两个map,一个存储key与Node,一个存储Node属于哪一个桶。

插入数据:假如map中包含该元素,那么更新value,访问次数++,并将该元素移动到访问次数+1桶。假如没有该元素,直接将该元素插入到访问次数为1的桶。假如超过了size,那么删除第一个桶的尾节点。

get数据:访问次数++,移动到访问次数+1的桶。

并查集

检查两个元素是否属于一个集合。

合并两个元素各自所在的所有集合。

并查集需要一次性将所有的数据传给他.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class UnionFind<K> {
//需要两个map,一个来存储元素的代表节点,一个来存储代表节点:集合大小
Map<K,K> fatherMap ;
Map<K,Integer> sizeMap;
//初始化并查集,并查集需要一次性将所有的数据传给他,初始化每一个元素代表一个集合,代表节点是他本身,size = 1
public UnionFind(List<K> nodes){
fatherMap = new HashMap<>();
sizeMap = new HashMap<>();
for(K node:nodes){
fatherMap.put(node,node);
sizeMap.put(node,1);
}
}

//寻找代表节点,通过不断的迭代寻找自己的上层节点,知道找到最顶层,需要注意的是,有一个路径压缩的问题,在node寻找代表节点的过程中
//他经过的所有父节点都需要将自己的父节点更新为代表节点
public K findHead(K node){
//stack中存储寻找代表节点过程中经历的所有父节点
Stack<K> stack = new Stack<>();
//寻找父节点
while(node!=fatherMap.get(node)){
stack.push(node);
node = fatherMap.get(node);
}
//路径压缩
while(!stack.isEmpty()){
fatherMap.put(stack.pop(),node);
}
return node;
}

/**
* 合并两个集合
* 两个节点各自寻找到自己的代表节点,
* 两个代表节点一致,不用合并
* 将小集合的元素合并到大集合元素中
* */

public void union(K a,K b){
K aHead = findHead(a);
K bHead = findHead(b);
if(aHead!=bHead) {
int aRank = sizeMap.get(aHead);
int bRank = sizeMap.get(bHead);
//a集合大,直接将bHead设置为aHead的子节点,将a集合size+bRank,并将b集合的size抹除
if(aRank>bRank){
fatherMap.put(bHead,aHead);
sizeMap.put(aHead,aRank+bRank);
sizeMap.remove(bHead);
}else{
fatherMap.put(aHead,bHead);
sizeMap.put(bHead,aRank+bRank);
sizeMap.remove(aHead);
}
}
}
}

前缀树

经常用于统计和排序大量的字符串(但不仅限于字符串)。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。

前缀匹配

trie树前缀匹配常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能

字符串检索

给定一组字符串,查找某个字符串是否出现过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
public class Trie {
public class TrieNode{
//该节点被经过了几次,可以用来求以其为前缀的字符串有多少
int path;
//该点为终止节点次数,可以用于求解某个字符串的个数
int end;
//字符串a-z一共有26个分支
TrieNode[] nexts ;
public TrieNode(){
this.path = 0;
this.end = 0;
nexts = new TrieNode[26];
}
}
//根节点,根节点是不存数值的
private TrieNode root;
/** Initialize your data structure here. */
public Trie() {
root = new TrieNode();
}

/**
* 插入元素。
* "XXXX"转化成字符数组,遍历整个数组,假如nexts[i]不存在,说明第一次遇到该字符,新建一个TrieNode,另next[i]=TrieNode,并path++,
* 当遍历完成整个数组,在最后一个TrieNode的end++,代表"XXXX"出现了几次
* */
public void insert(String word) {
if(word==null){
return ;
}
char[] values = word.toCharArray();
TrieNode node = this.root;
node.path++;
for(int i=0;i<values.length;i++){
int index = values[i]-'a';
if(node.nexts[index]==null){
node.nexts[index] = new TrieNode();
}
node = node.nexts[index];
node.path++;
}
node.end++;
}

/** 查找元素,与插入元素类似,需要注意的是最后一个节点的end值必须>1*/
public boolean search(String word) {
if(word==null){
return false;
}
TrieNode node = root;
char[] vals = word.toCharArray();
for(int i=0;i<vals.length;i++){
int index = vals[i]-'a';
if(node.nexts[index]==null){
return false;
}
node = node.nexts[index];
}
return node.end!=0;
}

/** 查询是否存在以某字符串为前缀 */
public boolean startsWith(String prefix) {
if(prefix==null){
return false;
}
TrieNode node = root;
char[] vals = prefix.toCharArray();
for(int i=0;i<vals.length;i++){
int index = vals[i]-'a';
if(node.nexts[index]==null){
return false;
}
node = node.nexts[index];
}
return true;
}
/**
* 删除元素
* 首先寻找是否在前缀树中,假如在,在前缀树中依次寻找节点,将path--,并将最后一个字符end--
* 需要注意的是,假如某个节点path--==0,那么后续的节点可以直接不用判断,直接全部删除
* */
public void delete(String word){
if(search(word)){
TrieNode node = root;
char[] vals = word.toCharArray();
for(int i=0;i<vals.length;i++){
int index = vals[i]-'a';
if(node.nexts[index].path--==1){
node.nexts[index]=null;
return ;
}
node = node.nexts[index];
}
node.end--;
}
}
}

leetcode基础算法

发表于 2019-12-05 | 分类于 leetcode

排序算法

算法 稳定性 时间复杂度 空间复杂度 备注
选择排序 × N^2 1
冒泡排序 √ N^2 1
插入排序 √ N ~ N^2 1 时间复杂度和初始顺序有关
希尔排序 × N 的若干倍乘于递增序列的长度 1 改进版插入排序
快速排序 × NlogN logN
三向切分快速排序 × N ~ NlogN logN 适用于有大量重复主键
归并排序 √ NlogN N
堆排序 × NlogN 1 无法利用局部性原理

快速排序

快排的最坏时间复杂度,选的初始值是最大值或者是最小值,这样就是N-1+N-2+….1=O(n^2).

最优的情况下空间复杂度为:O(logn) ;每一次都平分数组的情况,就是二叉树的层数

最差的情况下空间复杂度为:O( n ) ;退化为冒泡排序的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  /*
* 1.确定分界点(nums[l],nums[r],nums[(l+r)/2])
* 2.调整区间,左区间<=x,右区间>=x
* 使用两个指针来实现,一个指向头,一个指向尾部,假如分界点取nums[i],那么尾部指针先动,走向中间
* 当某个数<x,该指针停止,头部指针开始往中间走,当某个数>x,停止,将尾部指针指向的<x的数与头部指向的数交换
* 就可以达到目的,直到两个指针相遇
*
* 3.递归左右两个子区间
* */
public void quick_sort(int nums[],int l,int r){
if(l>=r)
return ;
int left = l-1,right = r+1;
int base = nums[(l+r)>>1];
while(left<right){
do{
left++;
}while(nums[left]<base);

do{
right++;
}while(nums[right]>base);
if(left<right){
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
//进行递归,分别判断左右两个子区间
quick_sort(nums,l,right);
quick_sort(nums,right+1,r);
}

改进版的快速排序,分为<num等于num以及大于num三个子区间,这样的话当数据中有大量相同的数据,就可以大大节省时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void quick_sort2(int[] nums,int left,int right){
if(left>=right){
return ;
}
int mid = (left+right)/2;
//拆分为三个子区间
int less =left-1;
int more = right +1;
int cur = left;
int target = nums[mid];
while(cur!=more){
if(nums[cur]<target){
less++;
swap(nums,cur,less);
cur++;
}else if(nums[cur]>target){
more--;
swap(nums,cur,more);

}else{
cur++;
}
}

if(less>left) {
quick_sort2(nums, left, less);
}
if(more<right) {
quick_sort2(nums, more, right);
}
}

75.荷兰国旗问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 其实就是< num放左边,==num放中间,>num放右边的问题
* 准备三个指针,less表示小于num的临界指针,cur当前元素的指针,大于num表示大于num的临界指针
* 当nums[cur]<target,将当前值与less临界指针阿下一个元素交换,less指针后移,cur后移
* 当nums[cur]==target,cur后移,无其它操作
* 当nums[cur]>target,将该元素与more指针的前一个元素交换,more前移
* */
public void sortColors(int[] nums) {
if(nums.length<2){
return;
}
int target = 1;
//定义指针,<num为-1,>num为nums.length,cur==0
int less =-1;
int cur = 0;
int more = nums.length;
while(cur!=more){
if(nums[cur]<target){
swap(nums,cur,less+1);
less++;
cur++;
}else if(nums[cur]==target){
cur++;
}else{
swap(nums,cur,more-1);
more--;
//注意,不需要cur++,因为交换的cur实际上可能<target
// cur++;
}
}
}
public void swap(int[] arr,int a,int b){
int tmp = arr[a];
arr[a] = arr[b];
arr[b] = tmp;
}

归并排序

合二为一。将两个排序好的数组进行合并,使用双指针。

空间复杂度:O(N),即tmp数组

时间复杂度:O(nlogN),可有公式求出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 1.确定分界点,取mid
* 2.递归排序左右两个子区间
* 3.归并两个排完序的子区间,使用双指针来解决
* */
public static void merge_sort(int[] nums,int l,int r){
if(l>=r)
return;
int mid = (l+r)>>1;
//递归排序
merge_sort(nums,l,mid);
merge_sort(nums,mid+1,r);
//临时存储合并的排序结果
int[] tmp = new int[r-l+1];
int index = 0;
int first = l;
int second = mid+1;
while(first<=mid&&second<=r){
if(nums[first]<=nums[second]){
tmp[index++] = nums[first++];
}else{
tmp[index++] = nums[second++];
}
}

while(first<=mid){
tmp[index++] = nums[first++];
}
while(second<=r){
tmp[index++] = nums[second++];
}
//将临时数组内容存储到nums中
int j = 0;
for(int i = l;i<=r;i++){
nums[i] = tmp[j++];
}
}

小和问题

主要借助归并排序来解决。类似于可以划分为左右子区间的问题都可以使用递归方式来解决。

1576392749776

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static int merge(int[] arr,int left ,int right){
int mid = left+((right-left)>>1);
if(left>=right){
return 0;
}
int left_result = merge(arr,left,mid);
int right_result = merge(arr,mid+1,right);
//合并
int[] tmp = new int[right-left+1];
int index = 0;
int first = left;
int secod = mid+1;
int res = 0;
//对排完序的左右子区间进行合并操作
//需要注意的是,假如左子区间的某个数i<右子区间的某一个数j,这个数必定就是右子区间j以及j之后的数的小数,因为两个子区间都是有序的
while(first<=mid&&secod<=right){
if(arr[first]<arr[secod]){
res += (right-secod+1)*arr[first];
tmp[index++] = arr[first++];
}else{
tmp[index++] = arr[secod++];
}
}
while(first<=mid){
tmp[index++] = arr[first++];
}
while(secod<=right){
tmp[index++] = arr[secod++];
}
index = 0;
for(int i=left;i<=right;i++){
arr[i] = tmp[index++];
}
//结果为两区间之和+合并区间
return left_result+right_result+res;

}

冒泡排序

时间复杂度为O(n^2)。n-1+n-2+….2=n^2/2

从左到右不断交换相邻逆序的元素,在一轮的循环之后,可以让未排序的最大元素上浮到右侧。

每一次循环都会有一个位置的元素确定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void bubbleSort(int[] arr){
if(arr==null||arr.length<2){
return;
}
for(int i=0;i<arr.length;i++){
for(int j=i+1;j<arr.length;j++){
if(arr[i]>arr[j]){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
}

选择排序

与冒泡排序差不多,与冒泡排序最大的区别就就是它的交换元素每一次只交换一次,选择排序是不稳定的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void selectSort(int[] arr){
if(arr==null||arr.length<2){
return;
}
int minindex = 0;
for(int i = 0;i<arr.length;i++){
minindex = i;
for(int j = i+1;j<arr.length;j++){
if(arr[j]<arr[minindex]){
minindex = j;
}
}
int tmp = arr[i];
arr[i] = arr[minindex];
arr[minindex] = tmp;
}
}

插入排序

时间复杂度最差是O(n^2),逆序的情况下,最好是O(n),顺序的情况下。

1
2
3
4
5
6
7
8
9
10
public void insertSort(int[] arr){
for(int i=1;i<arr.length;i++){
int j=i-1;
while(j>=0&&arr[i]>arr[j]){
swap(arr,i,j);
i =j;
j=j-1;
}
}
}

堆排序

堆结构

堆的插入删除时间复杂度是logN,即树的高度,因此,效率很高。

堆实际上是一个完全二叉树,堆可以用数组来表示,位置 k 的节点的父节点位置为 (k-1)/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。

大顶堆:父节点的值大于他所有子孙的值。

小顶堆:父节点的值小于他所有子孙的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//上浮操作
//与祖先节点进行比较,假如小于祖先节点,那么替换掉祖先节点,某个点父节点为(i-1)/2
public void heapInsert(int[] arr,int i){
while(arr[i]>arr[(i-1)/2]){
swap(arr,i,(i-1)/2);
i = (i-1)/2;
}
}

//下沉操作
//heapsize为边界
public void heapify(int[] arr,int i,int heapsize){
//i的左节点
int left = i*2+1;
//假如该节点它的某个子孙节点小,那么,就将二者交换
while(left <= heapsize){
//获取左右节点最大值
int right = left+1;
int largest = 0;
if(right<=heapsize){
largest = arr[right]>arr[left]?right:left;
}else {
largest = left;
}
//该节点与其孩子节点进行比较
largest = arr[i]>arr[largest]?i:largest;
//假如该节点大于其左右孩子,那么直接结束循环
if(largest==i){
break;
}
//该节点小于左或者右孩子,进行交换,继续循环
swap(arr,i,largest);
i = largest;
left = i*2+1;
}
}

/**
* 1.构建堆,通过上浮来完成
* 2.从构建好的堆中取根节点,并将最后一个节点填补上来
* */
public void heapSort(int[] arr){
//构建堆
for(int i=0;i<arr.length;i++){
heapInsert(arr,i);
}
//取节点
int heapsize = arr.length-1;
while(heapsize>0){
swap(arr,0,heapsize);
heapsize--;
heapify(arr,0,heapsize);
}
if (arr == null || arr.length < 2) {
return;
}
}

非基于比较器的排序

桶排序,基数排序,计数排序。

1576392783112

采用桶的思想来解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 假如数组有n个元素,那么准备n+1个桶
* 遍历一次数组,求出最大值最小值,将各个桶按照最大值最小值均分,比如,min=0,max=99,则桶为0~9,10~19.....90~99
* 这样的话,第一个桶必定有一个元素,即最小值,最后一个桶必定会有一个最大值
* 因此,需要将n-2个元素插入剩下的n+1个桶中,这样必定至少会有一个空桶
* 这样的话,空桶的左右两侧肯定各有一个非空桶,这样的话,最大差值一定不可能在桶内元素,而是在桶间元素
* 因此,我们只需要记录各个桶的最大值最小值,然后跨桶的最小值-最大值中必定含有最大差值
* */
public int maxGap(int[] arr){
if(arr==null||arr.length<2){
return 0;
}
int length = arr.length;
//寻找min max
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for(int i=0;i<length;i++){
min = Math.min(min,arr[i]);
max = Math.max(max,arr[i]);
}
if(min==max){
return 0;
}
//桶是否有数据,各桶最大值,最小值
boolean[] hasNum = new boolean[length+1];
int[] maxs = new int[length+1];
int[] mins = new int[length+1];
int bucketNum=0;
for(int i=0;i<length;i++){
//计算属于哪一个桶
bucketNum = getBucket(arr[i],length,min,max);
//桶内最大值,最小值
maxs[bucketNum] = hasNum[bucketNum]?Math.max(arr[i],maxs[bucketNum]):arr[i];
mins[bucketNum] = hasNum[bucketNum]?Math.min(arr[i],mins[bucketNum]):arr[i];
hasNum[bucketNum] = true;
}
//获取桶间元素最大值
int maxBetweenBucket = 0;
int lastMax = maxs[0];
for(int i=1;i<length+1;i++){
if(hasNum[i]) {
maxBetweenBucket = mins[i] - lastMax > maxBetweenBucket ? (mins[i] - lastMax) : maxBetweenBucket;
lastMax = maxs[i];
}
}
return maxBetweenBucket;
}

递归程序时间复杂度计算

1576392803920

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 后序遍历二叉树。
* 左子树返回不为空,右子树返回不为空,则该node必定是公共祖先
* */
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//当前节点==p或者==q,那么该点必定是祖先
if(root==null||root==p||root==q){
return root;
}
//递归左右子树
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
//假如左右子树返回均不为空,那么当前节点就是公共祖先
if(left!=null&&right!=null){
return root;
}
//只有一个子树返回的不是空
return left!=null?left:right;
}

该递归程序每次需要递归两次子区间,则a = 2,每个递归子区间大小是N/2,因此b = 2,而每次执行完还需要做O(1)的操作,因此,d= 0,log(b,a)=1>d,所以复杂度为O(N^log(2,2))=O(N)

工程中的排序算法

一般来说,是综合多种排序算法的。

假如数据量很小,使用插入排序;样本量很大,数据类型是基础类型,使用快排,数据类型是类,使用归并排序,可以保证数据稳定性。

leetcode之链表问题

发表于 2019-12-02 | 分类于 leetcode

19.删除链表的倒数第N个节点

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public ListNode removeNthFromEnd(ListNode head, int n) {
/**
* 因为要删除某个节点,必须先要获得它的前一个结点
* 因为有可能删除头节点,所以最好在头节点之前插入一个虚拟头结点
* 删除倒数第n个节点,需要找到倒数n+1个节点,最后一个节点与倒数n+1个节点相差n个节点
* 所以,准备first,second两个节点,保证他们距离是n,当second指到尾节点的时候,first恰好指向倒数n+1个节点
* */

//虚拟头节点
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode first = dummy;
ListNode second = dummy;
for(int i=0;i<n;i++){
first = first.next;
}
while(first.next!=null){
first = first.next;
second = second.next;
}
second.next = second.next.next;
return dummy.next;
}

237. 删除链表中的节点

1
2
3
4
5
6
7
8
/**
* 使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
* 将该节点的值设置为下一个节点的值,并将下一个节点删除
* */
public void deleteNode(ListNode node) {
node.data = node.next.data;
node.next = node.next.next;
}

83. 删除排序链表中的重复元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
*
* 当某个元素与后继元素相等时,直接删除;不等时,指针后移
* */
public ListNode deleteDuplicates(ListNode head) {
ListNode cur = head;
while(cur!=null&&cur.next!=null){
if(cur.val == cur.next.val){
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}
return head;
}

61.旋转链表

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public ListNode rotateRight(ListNode head, int k) {
//尾指针指向头节点,倒数第k+1个节点指向null,头指针指向倒数第k个
if(head==null){
return null;
}
//求链表长度
ListNode p =head;
int n =0;
while(p!=null){
n++;
p=p.next;
}
int len = k%n;
ListNode first = head;
ListNode second = head;
for(int i=0;i<len;i++){
first = first.next;
}
while(first.next!=null){
first = first.next;
second = second.next;
}
first.next = head;
head =second.next;
second.next = null;
return head;
}

24.两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

1575286645787

1
2
3
4
5
//需要交换a,b两个节点时
p.next = b
a.next = b.next
b.next = a
p =a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public ListNode swapPairs(ListNode head) {
//因为头节点也可能会改变,所以需要一个哑节点
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode cur = dummy;
while(cur.next!=null&&cur.next.next!=null){
ListNode a= cur.next;
ListNode b = a.next;
cur.next = b;
a.next = b.next;
b.next = a;
cur = a;
}
return dummy.next;
}

206.反转链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public ListNode reverseList(ListNode head) {
if(head==null){
return null;
}
ListNode pre = head;
ListNode cur = head.next;
while(cur!=null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
head.next=null;
head = pre;
return head;
}

92.反转链表2

1575293242449

1575294593614

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public ListNode reverseBetween(ListNode head, int m, int n) {
if(head==null){
return null;
}
if(m==n){
return head;
}
ListNode dummy = new ListNode(-1);
dummy.next = head;
//首先,需要找到prem以及nextn
ListNode premNode = dummy,nextnNode=dummy;
for(int i=0;i<m-1;i++){
premNode = premNode.next;
}
for(int i=0;i<n+1;i++){
nextnNode = nextnNode.next;
}


ListNode pre = premNode;
ListNode cur = premNode.next;
ListNode mNode = cur;
//将m,n之间元素全部反转
while(cur!=null&&cur!=nextnNode){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
//重新指向
premNode.next = pre;
mNode.next = nextnNode;

return dummy.next;

}

160.相交链表

1575377792658

编写一个程序,找到两个单链表相交的起始节点。

求相交的点,两个指针,第一个指针从a走,走a+c,到达尾部,再从b走;

第二个指针从b走,走b+c,到达尾部,再从a走。

这样,两个指针都走了a+b+c,相交点即为所求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode first = headA;
ListNode second = headB;
while(first!=second){
if(first!=null){
first = first.next;
}else{
first = headB;
}

if(second!=null){
second = second.next;
}else{
second = headA;
}
}
return first;
}

234. 回文链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 遍历链表,将值入栈。
* 从头遍历链表,将栈顶元素出栈,出栈顺序就是链表元素的逆序元素
* */
public boolean isPalindrome(ListNode head) {
Stack<Integer> help = new Stack<>();
ListNode cur = head;
//链表元素入栈
while(cur!=null){
help.push(cur.val);
cur = cur.next;
}
//从头重新遍历链表,与栈内元素比较,出栈即为链表的逆序
cur = head;
while (cur!=null){
int val = help.pop();
if(val==cur.val){
cur = cur.next;
}else{
return false;
}
}
return true;
}

空间复杂度为O(1)的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* 空间复杂度为O(1)的情况。
* 两个指针,快指针走两步,慢指针走一步。当快指针走完的时候,慢指针刚好走到终点,
* 从中点开始,往后逆转链表,这样,就会变成1->2->3<-2<-1
* 这样,两个指针,一个从头开始,一个从尾部开始,遍历链表,直到两者相遇
* */
public boolean isPalindrome(ListNode head) {
if(head==null||head.next==null){
return true;
}
ListNode slow = head;
ListNode fast = head;
while(fast!=null&&fast.next!=null){
fast = fast.next.next;
slow = slow.next;
}
//反转链表
ListNode pre = slow;
ListNode cur = slow.next;
slow.next = null;
while(cur!=null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
//头尾两个指针遍历
ListNode first = head;
ListNode second = pre;
while(second!=null&&first!=null){
if(first.val!=second.val){
return false;
}else{
first = first.next;
second = second.next;
}
}
//将逆转的链表恢复
ListNode n1 = pre;
ListNode n2 = pre.next;
pre.next=null;
while(n2!=null){
ListNode tmp = n2.next;
n2.next = n1;
n1 = n2;
n2 = tmp;
}
return true;
}

86. 分隔链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public ListNode partition(ListNode head, int x) {
if(head==null){
return null;
}
ListNode small = new ListNode(-1);
ListNode large = new ListNode(-1);
ListNode small_tail = small;
ListNode large_tail = large;
ListNode cur = head;
while(cur!=null){
int val = cur.val;
if(val<x){
ListNode node = new ListNode(val);
small_tail.next = node;
small_tail = node;
}else{
ListNode node = new ListNode(val);
large_tail.next = node;
large_tail = node;
}
cur = cur.next;
}
small_tail.next = null;
large_tail.next =null;
small_tail.next = large.next;
return small.next;
}

138. 复制带随机指针的链表

通过哈希表来解决,空间复杂度O(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Node copyRandomList(Node head) {
if(head==null){
return null;
}
Map<Node,Node> map = new HashMap<>();
Node cur = head;
//遍历链表,建立map映射
while(cur!=null){
Node copy = new Node(cur.val,null,null);
map.put(cur,copy);
cur = cur.next;
}
//对链表指针进行拷贝
cur = head;
while(cur!=null) {
if (cur.next != null) {
map.get(cur).next = map.get(cur.next);
}
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
return map.get(head);
}

空间复杂度为O(1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**遍历链表,建立一个包含原节点以及复制节点1->1'->2->2'->3->3'
* 对复制节点处理random指针
* 从链表中提取复制节点
* @param head
* @return
*/

public Node copyRandomList(Node head) {
if(head==null){
return null;
}
//1->1'->2->2'->3->3'
Node cur = head;
while (cur!=null){
Node copy = new Node(cur.val,null,null);
Node tmp = cur.next;
copy.next = cur.next;
cur.next = copy;
cur = tmp;
}
//处理random 1' random就是1 random.next.next
cur = head;
while(cur!=null){
cur.next.random =cur.random==null?null:cur.random.next;
cur = cur.next.next;
}
//分离链表
cur = head;
Node copyHead = head.next;
while(cur.next!=null){
Node tmp = cur.next;
cur.next = cur.next.next;
cur = tmp;
}
return copyHead;
}

两个链表相交问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
* 两个可能存在环的链表,需要多种情况讨论:
* 1.两个链表都没有环,判断相交
* 2.一个有环,一个没有环,必定不可能相交
* 3.两个都有环,判断相交
* 判断是否有环:快慢指针
* 判断无环链表相交:遍历两个链表,记录长度以及尾部节点,若两个尾部节点不等,不相交;若相等,寻找相交点。
* 短的链表先走len1-len2步,然后两个链表一起走,两个链表相等的时候就是交点。
* 判断两个有环链表相交:
* 分为三种情况:
* loop1=loop2,必定有交点,可以转化为无环链表相交问题
* loop1!=loop2
* 相交以及不相交
* 从链表a的环入点开始走,假如走完一圈没有碰到链表b的环入点,那么就是不相交的。
* */

public ListNode getIntersectNode(ListNode head1,ListNode head2){
if(head1==null||head2==null){
return null;
}
ListNode loop1 = detectCycle(head1);
ListNode loop2 = detectCycle(head2);
//均没有环,都有环
if(loop1==null&&loop2==null){
return bothLoop(head1,head2,loop1,loop2);
}else if(loop1!=null&&loop2!=null){
return noLoop(head1,head2);
}
//一个有环一个没有环必定不相交
return null;
}

//两个有环交点的判断
public ListNode bothLoop(ListNode head1,ListNode head2,ListNode loop1,ListNode loop2){
//假如loop1==loop2,可以看作是尾部节点是loop的两个无环链表相交问题
if(loop1==loop2){
ListNode cur1 = head1;
ListNode cur2 = head2;
int n=0;
while (cur1!=loop1){
n++;
cur1 = cur1.next;
}
while (cur2!=loop2){
n--;
cur2 = cur2.next;
}
ListNode first = head1;
ListNode second = head2;
if(n>0){
for(int i=0;i<n;i++) {
first = first.next;
}
}else{
for(int i=0;i<Math.abs(n);i++){
second = second.next;
}
}
while (first!=second){
first = first.next;
second = second.next;
}
return first;
}else{
//假如loop1!=loo2,从loop1开始走一圈,若有相交点,走这一圈中必定有loop1==loop2
ListNode cur = loop1.next;
while(cur!=loop1){
if(cur==loop2){
return loop1;
}else {
cur = cur.next;
}
}
}
return null;
}
//无环链表的交点判断
public ListNode noLoop(ListNode head1, ListNode head2) {
if(head1==null||head2==null){
return null;
}
//获取两个节点的尾部节点以及长度
ListNode cur1 = head1;
int length = 0;
while (cur1.next != null) {
length++;
cur1 = cur1.next;
}
ListNode cur2 = head2;
while (cur2.next != null) {
length--;
cur2 = cur2.next;
}
if (cur1 != cur2) {
return null;
}
//短的先走
ListNode first = head1;
ListNode second = head2;
if (length < 0) {
for(int i=0;i<Math.abs(length);i++) {
second = second.next;
}
}else{
for(int i=0;i<length;i++) {
first = first.next;
}
}
//同时走,寻找相交点
while (first != second) {
first = first.next;
second = second.next;
}
return first;
}
/**
* 寻找环的入口处
* */
//寻找环的入口点
public ListNode detectCycle(ListNode head) {
if(head==null){
return null;
}
ListNode interact = getInteract(head);
if(interact==null){
return null;
}
ListNode first = head;
ListNode second = interact;
while(first!=second){
first = first.next;
second = second.next;
}
return first;
}
//寻找相遇点
public ListNode getInteract(ListNode head){
ListNode slow = head;
ListNode fast = head;
while(fast!=null&&fast.next!=null){
fast = fast.next.next;
slow = slow.next;
if(fast==slow){
return fast;
}
}
return null;
}

哑节点问题

假如需要操纵节点的前置节点,那么在头节点前面添加一个哑节点。

1
2
3
//跳出while循环,此时cur指向的是最后一个节点
while(cur.next!=null){
}

数据库原理

发表于 2019-11-04 | 分类于 数据库

事务

一个数据库事务通常包含对数据库进行读或写的一个操作序列。

事务有四大特性:原子性,一致性,持久性,隔离性。

事务就是由一跳或者多条SQL语句组成的,事务中的操作要么不做,要么全做。

ACID

原子性

事务的所有操作要么全部执行成功,要么全部失败回滚。要做就全做,不然不做。

回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。

一致性

事务应确保数据库的状态从一个一致状态转变为另一个一致状态。在事务开始之前以及事务结束之后,数据库的完整性约束没有被破坏。

隔离性

隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

持久性

一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。

使用重做日志来保证持久性。

事务分类

主要分为扁平事务,带保存点的扁平事务,链事务,嵌套事务,分布式事务。

扁平事务

begin work 开始,commit work或者rollback work结束。

要么都执行,要么从头开始回滚。

他的限制是无法回滚或者提交数据的一部分,一旦回滚就得回滚所有。

带保存点的事务

回滚操作可以选择回滚到某一个保存点。rollback work: 2,回滚到第二个保存点。

并发事务带来的问题

多个事务并发执行,经常会发生多个事务操作相同的数据,这就会导致一定的一致性问题。

丢失修改

T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。T1再读取的时候就不是自己修改的数据了,而是T2修改之后的。

img

读脏数据

T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。

img

不可重复读

指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

img

幻影读

幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

img

不可重复读的重点是修改,幻读的重点在于新增或者删除。

事务隔离级别

通过事务隔离可以解决上面的问题。

读取未提交

最低的隔离级别,即使没有提交,其他事务对于该事务操作也是可见的。

可能会导致脏读、幻读或不可重复读。

leetcode之二分查找问题

发表于 2019-11-03 | 分类于 leetcode
  • 单调性问题
  • 存在着两段性的性质,即一半满足某一个性质,另一半不满足某个性质。

均可以使用二分查找。

1575181703866

红色表示满足某一个性质,绿色表示不满足某一个性质。

模板一

求红色的右边界情况。

if mid in 红 [L,R]->[M,R] L=M

else mid in 绿 [L,R]->[L,M-1] R=M-1

注意,mid应该是取(L+R)/2+1,防止死循环

1
2
3
4
5
6
7
8
9
while(l<r){
int mid = start+(end-start+1)/2;
if(check(mid)){
l = mid;
}else{
r = mid-1;
}
return l;
}

模板二

求绿色的左边界情况

if mid in 绿 [L,R]->[L,M] R=M

else mid in 红 [L,R]->[M+1,R] L=M+1

mid取值是(L+R)/2

1
2
3
4
5
6
7
8
9
while(l<r){
int mid = l+(r-l)/2;
if(check(mid)){
r = mid;
}else{
l = mid+1;
}
return l;
}

二分流程

确定二分边界:left = 0,right=x

编写二分代码框架

设定一个性质

判断区间如何更新

如果更新方式是l=mid,r=mid-1,那么mid上取整

69.求开方

按照框架来做:

设定一个性质:t*t<=x

区间如何更新:满足区间的右边界点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

//check:mid*mid<=target 取左侧边界
public int mySqrt(int x) {
if(x<=1){
return x;
}
int left = 1;
int right = x;
while(left<right){
int mid = left+(right-left+1)/2;
//防止溢出
if(mid<=x/mid){
left = mid;
}else{
right = mid-1;
}
}
return left;
}

35.搜索插入位置?

边界判断

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int searchInsert(int[] nums, int target) {
if(nums[nums.length-1]<target){
return nums.length;
}
//设定一个性质,t>=target,所以取右侧的左边界点
int l = 0;
int r = nums.length-1;
while(l<r){
int mid = l+(r-l)/2;
if(nums[mid]>=target){
r= mid;
}else{
l = mid+1;
}
}
return l;
}

34. 在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public int[] searchRange(int[] nums, int target) {
if(nums.length==0){
return new int[]{-1,-1};
}
int[] res = new int[2];
//寻找左边界,check:nums[mid]<target 右侧的边界点作为结果
int l= 0;
int r= nums.length-1;
while (l<r){
int mid = l+(r-l)/2;
if(nums[mid]<target){
l = mid+1;
}else{
r = mid;
}
}
if(nums[left]!=target){
return new int[]{-1,-1};
}else{
res[0] = left;
}

//寻找右边界.check:nums[mid]<=target, 左侧的边界点作为结果
l = 0;
r = nums.length-1;
while(l<r){
int mid = l+(r-l+1)/2;
if(nums[mid]<=target){
l = mid;
}else{
r = mid-1;
}
}
res[1] = left;
return res;
}

74.搜索二维矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//check:nums[mid]<=target 左侧边界值
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix.length==0||matrix[0].length==0){
return false;
}
int l = 0;
int m = matrix.length;
int n = matrix[0].length;
int r = m*n-1;
while(l<r){
int mid = l+(r-l+1)/2;
int first_dim = mid/n;
int second_dim = mid%n;
if(matrix[first_dim][second_dim]<=target){
l = mid;
}else{
r = mid-1;
}
}
return matrix[l/n][l%n]==target;
}

162.寻找峰值

峰值元素是指其值大于左右相邻值的元素。

给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。

数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 寻找峰值元素,当nums[mid]<nums[mid+1]时,表明mid右侧是有增加的,因为nums[n] = -∞,
* 所以mid右侧必定会有一个峰值点
* */
public int findPeakElement(int[] nums) {
int left = 0;
int right = nums.length-1;
while(left<right){
int mid = left+(right-left)/2;
if(nums[mid]<=nums[mid+1]){
left = mid+1;
}else{
right = mid;
}
}
return left;
}

基本的二分查找

搜索一个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int binarySearch(int [] arr,int val){
int start = 0;
int end = arr.length-1;
while(start<=end){//
int mid = start+(end-start)/2;
if(arr[mid]>val){
end = mid-1;//
}else if(arr[mid]<val){
start = end+1;//
}else{
return mid;
}
}
return -1;
}

744.寻找比目标字母大的最小字母

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//check:letters[mid]<=target 右侧边界
public char nextGreatestLetter(char[] letters, char target) {
if(target>=letters[letters.length-1]){
return letters[0];
}
int start = 0;
int end = letters.length-1;
while (start<end){
int mid = start+(end-start)/2;
if(letters[mid]<=target){
start = mid+1;
}else{
end = mid;
}
}
return letters[start];
}

540.有序数组的 Single Element

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数
* */
public int singleNonDuplicate(int[] nums) {
//当数组长度为1的时候
if(nums.length==1){
return nums[0];
}
//当单一元素在第一个位置或者在最后一个位置
if(nums[0]!=nums[1]){
return nums[0];
}
if(nums[nums.length-1]!=nums[nums.length-2]){
return nums[nums.length-1];
}

int start = 0;
int end = nums.length-1;
while(start<end){
int mid = start+(end - start)/2;
if((nums[mid]!=nums[mid-1])&&(nums[mid]!=nums[mid+1])){
return nums[mid];
}
/**mid为偶数,前面必定是偶数,因此,当nums[mid] == nums[mid-1], (奇数个) 3 3,因此,单个元素必定在前半段
* 当nums[mid] != nums[mid-1], (奇数个)2 3,因此,单个元素必定在后半段
* mid
* **/
if (mid % 2 == 0) {
if(nums[mid] == nums[mid-1]) {
end = mid -1;
}else{
start = mid;
}
}
/**mid 为奇数,前面必定为奇数,因此,当nums[mid] == nums[mid-1], (偶数个) 3 3,因此,单个元素必定在后半段
* 当nums[mid] != nums[mid-1], (偶数个)2 3,因此,单个元素必定在前
*
* */
if (mid % 2 == 1) {
if(nums[mid] == nums[mid-1]) {
start = mid;
}else{
end = mid-1;
}
}
}
return 0;
}

153.旋转数组的最小数字?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 假设按照升序排序的数组在预先未知的某个点上进行了旋转。
* ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
* 请找出其中最小的元素。
* */
public int findMin(int[] nums) {
//假如nums[mid]>最后一个元素,那么必定在[mid+1,r]之间
int l = 0;
int r = nums.length-1;
while (l<r){
int mid = l+(r-l)/2;
if(nums[mid]>nums[r]){
l = mid+1;
}else{
r = mid;
}
}
return nums[l];
}

leetcode之双指针问题

发表于 2019-11-02 | 分类于 leetcode

shuangzhi双指针可以将时间复杂度从O(n*n)到o(n)

而在双指针的概念中,我们可以将双指针分为两种类型:快慢指针、相向指针,同向指针。

左右指针中间夹,快慢指针走到头,后序指针往回走.

阅读全文 »
123…5
SKJin

SKJin

49 日志
12 分类
16 标签
RSS
GitHub E-Mail
© 2020 SKJin
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4