0%

回溯+约束编程小结

在刷Leetcode的时候,遇到了解数独和N皇后这两个问题,他们的解法有相类似的地方,记录一下。

解数独

题目描述

编写一个程序,通过已填充的空格来解决数独问题。

一个数独的解法需遵循如下规则:

  1. 数字 1-9在每一行只能出现一次。
  2. 数字 1-9在每一列只能出现一次。
  3. 数字 1-9在每一个以粗实线分隔的3x3宫内只能出现一次。

空白格用'.' 表示。

一个数独

答案被标成红色。

Note:

  • 给定的数独序列只包含数字 1-9 和字符 '.'
  • 你可以假设给定的数独只有唯一解。
  • 给定数独永远是 9x9 形式的。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sudoku-solver
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
class Solution {

int n = 3;
int N = n * n;
//分别代表每一行、列、方块中0-9填写的数字出现的次数
int [][] rows = new int[N][N + 1];
int [][] columns = new int[N][N + 1];
int [][] boxes = new int[N][N + 1];

char[][] board=null;
boolean sudokuSolved = false;

public void solveSudoku(char[][] board) {
this.board = board;
// 初始化
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
char num = board[i][j];
if (num != '.') {
int d = Character.getNumericValue(num);
placeNumber(d, i, j);
}
}
}
backtrack(0, 0);
}

public boolean couldPlace(int d, int row, int col) {
/*
* 检查该位置是否能放数字
*/
int idx = (row / n ) * n + col / n;
return rows[row][d] + columns[col][d] + boxes[idx][d] == 0;
}

public void placeNumber(int d, int row, int col) {
/*
Place a number d in (row, col) cell
*/
int idx = (row / n ) * n + col / n;

rows[row][d]++;
columns[col][d]++;
boxes[idx][d]++;
board[row][col] = (char)(d + '0');
}

public void removeNumber(int d, int row, int col) {
/*
Remove a number which didn't lead to a solution
*/
int idx = (row / n ) * n + col / n;
rows[row][d]--;
columns[col][d]--;
boxes[idx][d]--;
board[row][col] = '.';
}

public void placeNextNumbers(int row, int col) {

if ((col == N - 1) && (row == N - 1)) {
sudokuSolved = true;
}
// if not yet
else {
// if we're in the end of the row
// go to the next row
if (col == N - 1) backtrack(row + 1, 0);
// go to the next column
else backtrack(row, col + 1);
}
}

public void backtrack(int row, int col) {
/*
Backtracking
*/
// 如果这个位置为空
if (board[row][col] == '.') {
//从 0->9进行遍历
for (int d = 1; d < 10; d++) {
if (couldPlace(d, row, col)) {
placeNumber(d, row, col);
placeNextNumbers(row, col);
// if sudoku is solved, there is no need to backtrack
// since the single unique solution is promised
if (!sudokuSolved) removeNumber(d, row, col);
}
}
}
else placeNextNumbers(row, col);
}
}

解法参考:https://leetcode-cn.com/problems/sudoku-solver/solution/jie-shu-du-by-leetcode/

N皇后问题

题目描述

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

8-queens

上图为 8 皇后问题的一种解法。

给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。

每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
输入: 4
输出: [
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],

["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。

提示:

  • 皇后,是国际象棋中的棋子,意味着国王的妻子。皇后只做一件事,那就是“吃子”。当她遇见可以吃的棋子时,就迅速冲上去吃掉棋子。当然,她横、竖、斜都可走一到七步,可进可退。(引用自 百度百科 - 皇后 )

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/n-queens
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
class Solution {
//存储每一行皇后放的位置
int rows[];
// 主对角线
int hills[];
// 次对角线
int dales[];
int n;
List<List<String>> output ;
// 皇后位置,用于最后的设置
int queens[];

public boolean isNotUnderAttack(int row, int col) {
int res = rows[col] + hills[row - col + 2 * n] + dales[row + col];
return (res == 0) ? true : false;
}

public List<List<String>> solveNQueens(int n) {
output =new ArrayList<>();
this.n = n;
rows = new int[n];
//主对角线大一些是为了防止相减出现出现负数
hills = new int[4 * n - 1];
dales = new int[2 * n - 1];
queens = new int[n];

backtrack(0);
return output;
}

public void placeQueen(int row, int col) {
queens[row] = col;
rows[col] = 1;
//表示那条对角线被填
hills[row - col + 2 * n] = 1; // "hill" diagonals
dales[row + col] = 1; //"dale" diagonals
}

public void removeQueen(int row, int col) {
queens[row] = 0;
rows[col] = 0;
hills[row - col + 2 * n] = 0;
dales[row + col] = 0;
}

public void addSolution() {
List<String> solution = new ArrayList<String>();
for (int i = 0; i < n; ++i) {
int col = queens[i];
StringBuilder sb = new StringBuilder();
for(int j = 0; j < col; ++j) sb.append(".");
sb.append("Q");
for(int j = 0; j < n - col - 1; ++j) sb.append(".");
solution.add(sb.toString());
}
output.add(solution);
}

public void backtrack(int row) {
for (int col = 0; col < n; col++) {
if (isNotUnderAttack(row, col)) {
placeQueen(row, col);
// 放满了
if (row + 1 == n) addSolution();
// if not proceed to place the rest
else backtrack(row + 1);
// backtrack
removeQueen(row, col);
}
}
}
}

解题思想参考:https://leetcode-cn.com/problems/n-queens/solution/nhuang-hou-by-leetcode/

填充每个节点的下一个右侧节点指针小结

填充每个节点的下一个右侧节点指针

题目描述

给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

1
2
3
4
5
6
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL

初始状态下,所有 next 指针都被设置为 NULL

示例:

1
2
3
4
5
输入:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":null,"right":null,"val":4},"next":null,"right":{"$id":"4","left":null,"next":null,"right":null,"val":5},"val":2},"next":null,"right":{"$id":"5","left":{"$id":"6","left":null,"next":null,"right":null,"val":6},"next":null,"right":{"$id":"7","left":null,"next":null,"right":null,"val":7},"val":3},"val":1}

输出:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":{"$id":"4","left":null,"next":{"$id":"5","left":null,"next":{"$id":"6","left":null,"next":null,"right":null,"val":7},"right":null,"val":6},"right":null,"val":5},"right":null,"val":4},"next":{"$id":"7","left":{"$ref":"5"},"next":null,"right":{"$ref":"6"},"val":3},"right":{"$ref":"4"},"val":2},"next":null,"right":{"$ref":"7"},"val":1}

解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。

提示:

  • 你只能使用常量级额外空间。
  • 使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

理由已经建立好的next指针,进行递归

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
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;

public Node() {}

public Node(int _val) {
val = _val;
}

public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/

class Solution {
public Node connect(Node root) {
if(root==null || (root.left==null && root.right==null))
return root;

root.left.next = root.right;
//这一步很巧妙,利用了root已经设置好的next
if(root.next != null){
root.right.next = root.next.left;
}
//深度遍历
connect(root.left);
connect(root.right);
return root;
}
}

填充每个节点的下一个右侧节点指针II

题目描述

给定一个二叉树

1
2
3
4
5
6
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。

初始状态下,所有 next 指针都被设置为 NULL。

进阶:

  • 你只能使用常量级额外空间。
  • 使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。

示例:

1
2
3
输入:root = [1,2,3,4,5,null,7]
输出:[1,#,2,3,#,4,5,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。

提示:

  • 树中的节点数小于 6000
  • -100 <= node.val <= 100

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;

public Node() {}

public Node(int _val) {
val = _val;
}

public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/

class Solution {
public Node connect(Node root) {
if (root == null || (root.right == null && root.left == null)) {
return root;
}
if (root.left != null && root.right != null) {
root.left.next = root.right;
root.right.next = getNextNoNullChild(root);
}
if (root.left == null) {
root.right.next = getNextNoNullChild(root);
}
if (root.right == null) {
root.left.next = getNextNoNullChild(root);
}
//这里要注意:先递归右子树,否则右子树根节点next关系没建立好,左子树到右子树子节点无法正确挂载
root.right = connect(root.right);
root.left = connect(root.left);
return root;
}

/**
* 一路向右找到有子节点的根节点
*/
private Node getNextNoNullChild(Node root) {
while (root.next != null) {
if (root.next.left != null) {
return root.next.left;
}
if (root.next.right != null) {
return root.next.right;
}
root = root.next;
}
return null;
}
}

多数之和小结

在刷leetcode的时候发现了一类多数之和的问题,基本上都是采用排序+双指针解决,所有总结一下。

两数之和

题目描述

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:

1
2
3
4
5

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int[] twoSum(int[] nums, int target) {
if (nums==null || nums.length<=1) {
return null;
}
int []ans = new int[2];
Map<Integer,Integer> helperMap = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (helperMap.containsKey(target-nums[i]) && helperMap.get(target-nums[i])!=i) {
ans[0] = helperMap.get(target-nums[i]);
ans[1] = i;
break;
}
helperMap.put(nums[i],i);
}


if (ans[0]!=ans[1]) {
return ans;
}
return null;
}
}

这是一个很简单的题目。我们也可以使用排序+双指针解决,但是这题时间上最好的方法是哈希表,所以使用哈希表完成了。

两数之和II-输入有序数组

题目描述

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。

函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:

  • 返回的下标值(index1 和 index2)不是从零开始的。
  • 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

示例:

1
2
3
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

由于已经进行了排序我们直接使用双指针即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public int[] twoSum(int[] numbers, int target) {
if (numbers == null || numbers.length == 0) {
return null;
}

int p1 = 0;
int p2 = numbers.length - 1;
while (p1 < p2) {
if (numbers[p1] + numbers[p2] == target) {
return new int[] { p1+1, p2+1 };

} else if (numbers[p1] + numbers[p2] < target) {
++p1;
} else {
--p2;
}
}

return null;
}
}

三数之和

题目描述

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例:

1
2
3
4
5
6
7
给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);//利用排序解决重复问题
int p1 =0;
int p2 =nums.length-1;
int target = 0; //target可以为任意数
for (int i = 0; i < nums.length; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
p1=i+1;p2 = nums.length-1;
//转化为两数之和
while (p1<p2) {
int sum = nums[i]+nums[p1]+nums[p2];
if(sum==target){
List<Integer> ans = new ArrayList<>();
ans.add(nums[i]);
ans.add(nums[p1]);
ans.add(nums[p2]);
res.add(ans);
//解决重复问题
while(p1<p2&&nums[p1]==nums[p1+1]){
p1++;
}
p1++;
while (p1<p2&&nums[p2]==nums[p2-1]) {
p2--;
}
p2--;
}else if(sum<target){
p1++;
}else if(sum>target){
p2--;
}
}
}
return res;
}
}

为了解决重复以及转化为两数之和,我们使用排序加双指针完成

最接近的三数之和

题目描述

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

示例:

1
2
3
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

提示:

3 <= nums.length <= 10^3
-10^3 <= nums[i] <= 10^3
-10^4 <= target <= 10^4

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum-closest
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int p1 =0;
int p2 =nums.length-1;
int closetNum = nums[0]+nums[1]+nums[2];
for (int i = 0; i < nums.length; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
p1=i+1;p2 = nums.length-1;
int sum = 0;
//转化为两数之和
while (p1<p2) {
sum = nums[p1]+nums[p2]+nums[i];
//改变closetNum
if(Math.abs(sum-target)<Math.abs(closetNum-target)){
closetNum = sum;
}

if(sum==target){
return target;
}else if(sum<target){
p1++;
}else if(sum>target){
p2--;
}
}
}
return closetNum;
}
}

跟上一题思路相同,同样可以使用排序+双指针

四数之和

题目描述

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。

示例:

1
2
3
4
5
6
7
8
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/4sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);//利用排序解决重复问题
int p1 =0;
int p2 =nums.length-1;
//和三数之后思路相同
for (int i = 0; i < nums.length-3; i++) {
if(i > 0 && nums[i] == nums[i - 1]) continue; //确保第一个数发生变化
for (int j = i+1; j < nums.length-2; j++) {
if(j>i+1 && nums[j] == nums[j - 1]) continue; //确保第二个数发生变化
p1=j+1;p2 = nums.length-1;
//转化为两数之和
while (p1<p2) {
int sum = nums[i]+nums[j]+nums[p1]+nums[p2];
if(sum==target){
List<Integer> ans = new ArrayList<>();
ans.add(nums[i]);
ans.add(nums[j]);
ans.add(nums[p1]);
ans.add(nums[p2]);
res.add(ans);
//解决重复问题
while(p1<p2&&nums[p1]==nums[p1+1]){
p1++;
}
p1++;
while (p1<p2&&nums[p2]==nums[p2-1]) {
p2--;
}
p2--;
}else if(sum<target){
p1++;
}else if(sum>target){
p2--;
}
}
}
}
return res;
}
}

四数之和再三数之和的基础上再次升级,不过换汤不换药,我们还是使用一样的方法。

[toc]

字符串匹配问题小结

刷Leetcode时,发现有两个字符串匹配问题很巧妙,所以记录一下

正则表达式匹配

问题描述

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。

1
2
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素

所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

说明:

  • s 可能为空,且只包含从 a-z 的小写字母。

  • p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。

示例 1:

1
2
3
4
5
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:

1
2
3
4
5
输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。

示例 3:

1
2
3
4
5
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。

示例 4:

1
2
3
4
5
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。

示例 5:

1
2
3
4
输入:
s = "mississippi"
p = "mis*is*p*."
输出: false

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/regular-expression-matching
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

回溯思想:

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
class Solution {
char[] str;
char[] pattern;
public boolean isMatch(String s, String p) {
if (s==null||p==null) {
return false;
}
this.str = s.toCharArray();
this.pattern = p.toCharArray();
return matchCore(0,0);
}

/***
*
* @param sStart str 开始的位置
* @param pStart pattern 开始的位置
* @return
*/
boolean matchCore(int sStart,int pStart){
if (sStart==str.length && pStart==pattern.length) return true;
if (sStart!=str.length && pStart==pattern.length) return false;

//说明第二个为 *
if (pStart<pattern.length-1 && pattern[pStart+1]=='*') {
if (sStart!=str.length && (pattern[pStart]==str[sStart]||pattern[pStart]=='.')) {
//相等时,需要进行判断
//分别为 *当作一个,当作多个,当作没有
return matchCore(sStart+1, pStart+2)||matchCore(sStart+1, pStart) || matchCore(sStart, pStart+2);

}else{
//不相等时,当这个*不存在
return matchCore(sStart, pStart+2);
}
}

if (sStart!=str.length && (pattern[pStart]==str[sStart]||(pattern[pStart]=='.' )))
return matchCore(sStart+1, pStart+1);

return false;

}
}

回溯比较好理解,代码也比较好实现,总结来说就:

  • 当pattern[pStart]==str[sStart]||(pattern[pStart] ==’.’)时

    pattern和str都访问下一个就行

  • pattern[pStart+1]==’‘(第二个字符是‘ *’)时,分情况讨论:

    • pattern[pStart]== str[sStart]||pattern[pStart] ==’.’),那么进行递归判断,
      • sStart+1, pStart+2,‘*‘当一个字符
      • sStart+1, pStart,‘*‘当多个字符
      • sStart, pStart+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
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length();
int n = p.length();
//代表s的i和p的j的匹配
boolean[][] f = new boolean[m + 1][n + 1];
//表示没有字符时
f[0][0] = true;
for (int i = 0; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (p.charAt(j - 1) == '*') {
f[i][j] = f[i][j - 2];
if (matches(s, p, i, j - 1)) {
f[i][j] = f[i][j] || f[i - 1][j];
//下面几种都行
//dp[i][j] = dp[i - 1][j] || dp[i][j - 1] || dp[i][j - 2];
//dp[i][j] = dp[i - 1][j] || dp[i - 1][j - 2] || dp[i][j - 2]
}
}
else {
if (matches(s, p, i, j)) {
f[i][j] = f[i - 1][j - 1];
}
}
}
}
return f[m][n];
}

public boolean matches(String s, String p, int i, int j) {
if (i == 0) {
return false;
}
if (p.charAt(j - 1) == '.') {
return true;
}
return s.charAt(i - 1) == p.charAt(j - 1);
}
}

动态规划主要是要想出子问题以及动态转移方程。我们可以先画个表格,显示一下子问题的结构

c * a * b
a F F T T F
a T T F
b T

转移大概这样:

image-20200711200940453

状态转移方程的思路:
在上面表格的帮助下,我们可以确定dp[i][j]的含义, dp[i][j]表示 s 串的前 i个字符是否能被 p 的前 j 个字符匹配。所以有下列的式子:

  1. p[j] == s[i] : dp[i][j] = dp[i-1][j-1]

  2. p[j] == "." : dp[i][j] = dp[i-1][j-1]

  3. p[j] ==" * ":

    • p[j-1] != s[i] : dp[i][j] = dp[i][j-2],即'*'前一个字符和s串的第i个字符不匹配时,我们将该字符作为空处理,所以dp[i][j] = dp[i][j-2],比如(ab, abc * )这种情况

    • p[j-1] == s[i] or p[j-1] == ".",,即'*'前一个字符和s串的第i个字符匹配时,

      • dp[i][j] = dp[i-1][j] // 多个字符匹配的情况,这种思维上感觉最难理解,我个人是理解为(aaa,a*)这种情况,画个表格可以看出一些效果,类似于当s第一个a和q的第一个a匹配后,无论后面多几个a都不影响,只需要关注上一个(i-1)是否匹配。
      • dp[i][j] = dp[i][j-1] // 单个字符匹配的情况,这种情况其实是多余的,但是对应着上面回溯的做法,写出来比较好理解。为什么说多余?因为单个字符匹配的情况可以说是多个字符匹配的情况的特例,虽然数学上暂时我还不会证明,但是可以举出(aa,a*)这个例子。
      • dp[i][j] = dp[i][j-2] // 没有匹配的情况

进行归纳起来就是

  1. 如果 p.charAt(j) == s.charAt(i) : dp[i][j] = dp[i-1][j-1];
  2. 如果p.charAt(j) == '.' : dp[i][j] = dp[i-1][j-1];
  3. 如果p.charAt(j) == ' * '
    1. 如果p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2] //in this case, a* only counts as empty
    2. 如果 p.charAt(i-1) == s.charAt(i) or p.charAt(i-1) == '.':
      • dp[i][j] = dp[i-1][j] //in this case, a* counts as multiple a
      • or dp[i][j] = dp[i][j-1] // in this case, a* counts as single a
      • or dp[i][j] = dp[i][j-2] // in this case, a* counts as empty

具体可以参考:

通配符匹配

问题描述

给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。

1
2
'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。

两个字符串完全匹配才算匹配成功。

说明:

  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。

示例 1:

1
2
3
4
5
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:

1
2
3
4
5
输入:
s = "aa"
p = "*"
输出: true
解释: '*' 可以匹配任意字符串。

示例 3:

1
2
3
4
5
输入:
s = "cb"
p = "?a"
输出: false
解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。

示例 4:

1
2
3
4
5
输入:
s = "adceb"
p = "*a*b"
输出: true
解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".

示例 5:

1
2
3
4
输入:
s = "acdcb"
p = "a*c?b"
输出: false

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/wildcard-matching
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

回溯思想

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
class Solution {
char[] str;
char[] pattern;
public boolean isMatch(String s, String p) {
if (s==null||p==null) {
return false;
}
this.str = s.toCharArray();
this.pattern = p.toCharArray();
return matchCore(0,0);
}

/***
*
* @param sStart str开始的地方
* @param pStart pattern 开始的位置
* @return
*/
boolean matchCore(int sStart,int pStart){
if (sStart==str.length && pStart==pattern.length) return true;
if (sStart!=str.length && pStart==pattern.length) return false;
// if(sStart>=str.length && pattern[pStart]!='*') return false;

if(sStart<str.length && pattern[pStart]=='*'){
//分别为 * 代表空、1个字符、多个字符
return matchCore(sStart, pStart+1) || matchCore(sStart+1, pStart+1) || matchCore(sStart+1, pStart);
}else if (sStart<str.length && pattern[pStart]==str[sStart]||(pattern[pStart]=='?')) {
return matchCore(sStart+1, pStart+1);
}else if (sStart==str.length && pattern[pStart]=='*') {
return matchCore(sStart, pStart+1);
}

return false;
}
}

思路和上一题类似

动态规划:

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
class Solution {

public boolean isMatch(String s, String p) {
int m = s.length();
int n = p.length();
//dp[i][j表示 s 第i个 到p第j个是否匹配
boolean[][] dp = new boolean[m + 1][n + 1];
dp[0][0] = true;
for (int i = 1; i <=n; i++) {
if (p.charAt(i - 1) == '*') {
dp[0][i] = true;
}
else {
break;
}
}

for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (p.charAt(j - 1) == '*') {
dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
} else if (p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[m][n];
}

}

可以参照这个思路:https://leetcode-cn.com/problems/wildcard-matching/solution/yi-ge-qi-pan-kan-dong-dong-tai-gui-hua-dpsi-lu-by-/

总结

目前就记录这两题,后续再加

存在重复元素小结

小结Leetcode当中存在重复元素

217.存在重复元素

题目描述

给定一个整数数组,判断是否存在重复元素。

如果任意一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。

示例 1:

1
2
输入: [1,2,3,1]
输出: true

示例 2:

1
2
输入: [1,2,3,4]
输出: false

示例 3:

1
2
输入: [1,1,1,3,3,4,3,2,4,2]
输出: true

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/contains-duplicate
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

可以使用排序等方法,但是就这题来说,使用hash表最合适

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public boolean containsDuplicate(int[] nums) {
Set<Integer> set = new HashSet<>();

for (int num : nums) {
if (set.contains(num)) {
return true;
}
set.add(num);
}
return false;
}
}

219存在重复元素II

题目描述

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。

示例 1:

1
2
输入: nums = [1,2,3,1], k = 3
输出: true

示例 2:

1
2
输入: nums = [1,0,1,1], k = 1
输出: true

示例 3:

1
2
输入: nums = [1,2,3,1,2,3], k = 2
输出: false

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/contains-duplicate-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

同样可以使用哈希表维护一个滑动窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public boolean containsNearbyDuplicate(int[] nums, int k) {
// 用散列表来维护这个kk大小的滑动窗口。
Set<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length; ++i) {
if (set.contains(nums[i])) return true;
set.add(nums[i]);
if (set.size() > k) {
set.remove(nums[i - k]);
}
}
return false;
}
}

220.存在重复元素III

在整数数组 nums 中,是否存在两个下标 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值小于等于 t ,且满足 i 和 j 的差的绝对值也小于等于 ķ 。

如果存在则返回 true,不存在返回 false。

示例 1:

1
2
输入: nums = [1,2,3,1], k = 3, t = 0
输出: true

示例 2:

1
2
输入: nums = [1,0,1,1], k = 1, t = 2
输出: true

示例 3:

1
2
输入: nums = [1,5,9,1,5,9], k = 2, t = 3
输出: false

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/contains-duplicate-iii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

这题在第二题的基础上又加了一些条件,比如:差的绝对值小于等于 t

这个让我们使用hash表进行遍历不是很方便,所以我们使用二叉搜索树进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
TreeSet<Integer> set = new TreeSet<>();
for (int i = 0; i < nums.length; ++i) {
// 返回set中大于或者等于给定元素的最小元素,如果不存在这样的元素,返回null.
Integer s = set.ceiling(nums[i]);
if (s != null && s <= nums[i] + t)
return true;

// 返回在这个集合中小于或者等于给定元素的最大元素,如果不存在这样的元素,返回null.
Integer g = set.floor(nums[i]);
if (g != null && nums[i] <= g + t)
return true;
//维持滑动窗口
set.add(nums[i]);
if (set.size() > k) {
set.remove(nums[i - k]);
}
}
return false;
}
}

打家劫舍小结

Leetcode打家劫舍问题小结

198.打家劫舍

题目描述

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

1
2
3
4
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

1
2
3
4
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/house-robber
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

动态规划的思想,比较简单,设置dp[i]为第i家时金额最大

所以dp[i] = max(dp[i-1],dp[i-2]+nums[i])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int n = nums.length;

int[] dp = new int[n];
dp[0] = nums[0];
if (n > 1) {
dp[1] = Math.max(nums[0], nums[1]);
}
for (int i = 2; i < n; i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[n - 1];
}
}

进行降维,使用滚动数组,比较简洁优雅

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int rob(int[] nums) {
int pre = 0, cur = 0, tmp;
for (int num : nums) {
tmp = cur;
cur = Math.max(pre + num, cur);
pre = tmp;
}
return cur;
}
}

213.打家劫舍II

题目描述

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:

1
2
3
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

1
2
3
4
输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/house-robber-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

参考:https://leetcode-cn.com/problems/house-robber-ii/solution/213-da-jia-jie-she-iidong-tai-gui-hua-jie-gou-hua-/

这题和上面有一些小区别,因为第一个和最后一个房屋是相互关联的。所以第一个房屋和最后一个房屋只能选择一个。然后在看了题解之后,发现其实挺简单的,只需要遍历两次,第一次有第一个房屋,没有最后房屋,第二次没有第一个房屋有最后一个房屋

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
// 进行两次遍历,分别对0->n-2 和 1->n-1 进行求解
public int rob(int[] nums) {
if (nums.length == 0)
return 0;
if (nums.length == 1)
return nums[0];
return Math.max(myRob(Arrays.copyOfRange(nums, 0, nums.length - 1)),
myRob(Arrays.copyOfRange(nums, 1, nums.length)));
}

private int myRob(int[] nums) {
int pre = 0, cur = 0, tmp;
for (int num : nums) {
tmp = cur;
cur = Math.max(pre + num, cur);
pre = tmp;
}
return cur;
}
}

337.打家劫舍III

题目描述

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例 1:

1
2
3
4
5
6
7
8
9
10
输入: [3,2,3,null,3,null,1]

3
/ \
2 3
\ \
3 1

输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.

示例 2:

输入: [3,4,5,1,3,null,1]

1
2
3
4
5
6
7
8
	 3
/ \
4 5
/ \ \
1 3 1

输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/house-robber-iii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

主要参考:https://leetcode-cn.com/problems/house-robber-iii/solution/shu-xing-dp-ru-men-wen-ti-by-liweiwei1419/

学习到这是一个树形dp入门的题目,并且是一个和状态相关的dp,需要消除后效性,采用的方法是在设计状态的时候,在后面加一维,消除后效性

然后这一题用到的遍历是后序遍历,因为我们的逻辑是子结点陆续汇报信息给父结点,一层一层向上汇报,最后在根结点汇总值。然后我们的状态有两个偷还是不偷。

根据当前结点偷或者不偷,就决定了需要从哪些子结点里的对应的状态转移过来。

  • 如果当前结点不偷,左右子结点偷或者不偷都行,选最大者;
  • 如果当前结点偷,左右子结点均不能偷。
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
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {

public int rob(TreeNode root) {
int[] res = dfs(root);
return Math.max(res[0], res[1]);
}

private int[] dfs(TreeNode node) {
if (node == null) {
return new int[] { 0, 0 };
}

// 分类讨论的标准是:当前结点偷或者不偷
// 由于需要后序遍历,所以先计算左右子结点,然后计算当前结点的状态值
int[] left = dfs(node.left);
int[] right = dfs(node.right);

// dp[0]:以当前 node 为根结点的子树能够偷取的最大价值,规定 node 结点不偷
// dp[1]:以当前 node 为根结点的子树能够偷取的最大价值,规定 node 结点偷
int[] dp = new int[2];

dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
dp[1] = node.val + left[0] + right[0];
return dp;
}
}

总结

在这三题当中主要从第三天种学的多,学习到了dp的后效性,以及树的遍历多用后续遍历。

括号问题

刷Leetcode时,遇到过许多括号问题,总结一下

有效括号

给定一个只包括 ‘(‘,’)’,’{‘,’}’,’[‘,’]’ 的字符串,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

示例 1:

1
2
输入: "()"
输出: true

示例 2:

1
2
输入: "()[]{}"
输出: true

示例 3:

1
2
输入: "(]"
输出: false

示例 4:

1
2
输入: "([)]"
输出: false

示例 5:

1
2
输入: "{[]}"
输出: true

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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 Solution {
private HashMap<Character, Character> mappings;
public boolean isValid(String s) {
if(s==null||s.length()==0) return true;
this.mappings = new HashMap<Character, Character>();
this.mappings.put(')', '(');
this.mappings.put('}', '{');
this.mappings.put(']', '[');
Stack<Character> charStack = new Stack<>();

for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);

if (this.mappings.containsKey(ch)) {
char topElement = charStack.empty() ? '#' : charStack.pop();
if (topElement != this.mappings.get(ch)) {
return false;
}

}else{
charStack.push(ch);
}
}
return charStack.isEmpty();
}
}

括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例:

1
2
3
4
5
6
7
8
输入:n = 3
输出:[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/generate-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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 Solution {
List<String> res = null;
public List<String> generateParenthesis(int n) {
res = new ArrayList<>();
char [] Parenthesis = new char[2*n];
for (int i = 0; i < Parenthesis.length; i++) {
Parenthesis[i]='(';
}
dfs(Parenthesis,1,0);
return res;
}

void dfs(char[] Parenthesis,int index,int count){
if (count==Parenthesis.length/2) {
res.add(String.valueOf(Parenthesis));
return;
}

for (int i = index; i < Parenthesis.length; i++) {
if (i+1<(count+1)*2) continue;
Parenthesis[i] = ')';
dfs(Parenthesis,i+1,count+1);
Parenthesis[i]='(';
}
}
}

最长有效括号

给定一个只包含 ‘(‘ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。

示例 1:

1
2
3
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"

示例 2:

1
2
3
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-valid-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

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
/**
* 困难级别,以后需要回看一下
*/
public class longestValidParentheses {
//动态规划
public int LongestValidParentheses(String s) {
int maxans = 0;
int []dp = new int[s.length()];
for (int i = 1; i < dp.length; i++) {
if (s.charAt(i)==')') {

if (s.charAt(i-1)=='(') {
//刚好凑一对
dp[i]=(i>2 ? dp[i-2] : 0)+2;
}else if(i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '('){
//跟远方的凑成一对
dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
}
maxans = Math.max(maxans, dp[i]);
}
}
return maxans;
}

//使用栈的方法进行
public int LongestValidParentheses2(String s) {
int maxans = 0;
//将下标存进去
Stack<Integer> stack = new Stack<>();
stack.push(-1);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
stack.push(i);
} else {
stack.pop();
if (stack.empty()) {
stack.push(i);
} else {
maxans = Math.max(maxans, i - stack.peek());
}
}
}
return maxans;
}

//贪心算法
public int longestValidParentheses3(String s) {
int left = 0, right = 0, maxlength = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * right);
} else if (right > left) {
left = right = 0;
}
}
left = right = 0;
for (int i = s.length() - 1; i >= 0; i--) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * left);
} else if (left > right) {
left = right = 0;
}
}
return maxlength;
}

public static void main(String[] args) {
longestValidParentheses l = new longestValidParentheses();
String s = "()(())";
System.out.println(l.LongestValidParentheses(s));
}
}

排列组合题目小结

排列题目

全排列

题目描述

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

1
2
3
4
5
6
7
8
9
10
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

没有重复元素,比较简单,可以放心的进行交换

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
class Solution {
public List<List<Integer>> permute(int[] nums) {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
dfs(nums, res, 0);
return res;
}

private void dfs(int[]nums,List<List<Integer>> res,int index){
if (index==nums.length-1) {
List<Integer> path = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
path.add(nums[i]);
}
res.add(path);
}

for (int i = index; i < nums.length; i++) {
//排列树进行交换
swap(nums, index, i);
dfs(nums,res,index+1);
swap(nums, index, i);
}
}

private void swap(int[] cs,int i,int j){
int temp = cs[i];
cs[i] = cs[j];
cs[j] = temp;
}
}
}

全排列2

题目描述

给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

1
2
3
4
5
6
7
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

有重复的数字,进行排列时就会有重复,所以我们再交换的时候加入set,可以抵消这种交换带来的问题

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
class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
dfs(nums, res, 0);
return res;
}

public void dfs(int[]nums,List<List<Integer>> res,int index){
if (index==nums.length-1) {
List<Integer> path = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
path.add(nums[i]);
}
res.add(path);
return;
}
Set<Integer> intSet = new HashSet<Integer>();
for (int i = index; i < nums.length; i++) {
if (i==index || !intSet.contains(nums[i])) {
intSet.add(nums[i]);
swap(nums, index, i);
dfs(nums,res,index+1);
swap(nums, index, i);
}
}
}

private void swap(int[] cs,int i,int j){
int temp = cs[i];
cs[i] = cs[j];
cs[j] = temp;
}
}

下一个排列

题目描述

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须原地修改,只允许使用额外常数空间。

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,31,3,2
3,2,11,2,3
1,1,51,5,1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-permutation
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

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
class Solution {
public void nextPermutation(int[] nums) {
if (nums == null || nums.length <= 1) return;

int i = nums.length - 2;
//从后向前先找第一个从后看是降序。
//eg : 1,4,3,2 找到 1
while (i>-1 && nums[i+1]<=nums[i]) {
i--;
}

int j = nums.length - 1;
if(i>-1){

//找到刚好比nums[i]大的数
//eg : 1,4,3,2 中已经找到了1,所以接着找到2
while(nums[i]>=nums[j])j--;
swap(nums,i,j);
}
//出来变成 2,4,3,1

i++;
j = nums.length-1;
//将剩下的数进行逆序,变成小的在前面
//eg 2,4,3,1 -> 2,1,3,4
while (i<j) {
swap(nums,i,j);
i++;
j--;
}
}

void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}

思路其实比较简单,直接从后向前先找第一个从后看是降序,可以参考:https://leetcode-cn.com/problems/next-permutation/solution/xia-yi-ge-pai-lie-suan-fa-xiang-jie-si-lu-tui-dao-/

第k个排列

题目描述

给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。

按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:

  1. “123”
  2. “132”
  3. “213”
  4. “231”
  5. “312”
  6. “321”

给定 n 和 k,返回第 k 个排列。

说明:

  • 给定 n 的范围是 [1, 9]。
  • 给定 k 的范围是[1, n!]。

示例 1:

1
2
输入: n = 3, k = 3
输出: "213"

示例 2:

1
2
输入: n = 4, k = 9
输出: "2314"

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutation-sequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public String getPermutation(int n, int k) {
StringBuilder res = new StringBuilder();
StringBuilder num = new StringBuilder("123456789");
int[] f= new int[n];
f[0] = 1;
for (int i = 1; i < f.length; i++) {
f[i] = f[i-1]*i;
}
--k;
for (int i = n; i >=1; --i) {
int j = k/f[i-1];
k%=f[i-1];
res.append(num.charAt(j));
num.deleteCharAt(j);
}
return res.toString();
}
}

参考:https://www.cnblogs.com/ariel-dreamland/p/9149577.html

组合题目

子集

题目描述

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,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
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
ArrayList<Integer>ans = new ArrayList<>();
dfs(nums,ans,0);
return res;
}

/**
* 回溯
* @param nums
* @param ans
* @param n 从第n个数开始没有被访问
*/
public void dfs(int []nums,ArrayList<Integer>ans,int n){

res.add(new ArrayList(ans));
for (int i = n; i < nums.length; i++) {
ans.add(nums[i]);
dfs(nums, ans, i+1);
ans.remove(ans.size()-1);
}

}
}

子集II

题目描述

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

1
2
3
4
5
6
7
8
9
10
输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]

代码

有重复,需要进行排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
List<Integer>ans = new ArrayList<>();
dfs(nums,ans,0);
return res;
}

public void dfs(int []nums,List<Integer>ans,int n){
res.add(new ArrayList(ans));
for (int i = n; i < nums.length; i++) {
//剪枝情况
//这个判断需要理解
if (i > n && nums[i] == nums[i - 1] ) continue;
ans.add(nums[i]);
dfs(nums, ans, i+1);
ans.remove(ans.size()-1);
}

}
}

组合

题目描述

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

示例:

1
2
3
4
5
6
7
8
9
10
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combinations
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

为了和上面呼应,所以创建了nums这个数组,其实不需要创建也可以

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
class Solution {
List<List<Integer>> res = new LinkedList<>();
int k;
int []nums;
public List<List<Integer>> combine(int n, int k) {
nums = new int[n];
this.k=k;
for (int i = 0; i < nums.length; i++) {
nums[i] = i+1;
}
LinkedList<Integer> path = new LinkedList<>();
dfs(path,0);
return res;
}

void dfs(LinkedList<Integer> path,int n){
if (path.size()==k) {
res.add(new LinkedList(path));
return ;
}

for (int i = n; i < nums.length; i++) {
path.add(nums[i]);
dfs(path,i+1);
path.removeLast();
}
}
}

官方答案就没有创建,链接:https://leetcode-cn.com/problems/combinations/solution/zu-he-by-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
class Solution {
List<List<Integer>> output = new LinkedList();
int n;
int k;

public void backtrack(int first, LinkedList<Integer> curr) {
// if the combination is done
if (curr.size() == k)
output.add(new LinkedList(curr));

for (int i = first; i < n + 1; ++i) {
// add i into the current combination
curr.add(i);
// use next integers to complete the combination
backtrack(i + 1, curr);
// backtrack
curr.removeLast();
}
}

public List<List<Integer>> combine(int n, int k) {
this.n = n;
this.k = k;
backtrack(1, new LinkedList<Integer>());
return output;
}
}

组合总和

题目描述

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。

示例 1:

1
2
3
4
5
6
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]

示例 2:

1
2
3
4
5
6
7
输入:candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

提示:

  • 1 <= candidates.length <= 30
  • 1 <= candidates[i] <= 200
  • candidate 中的每个元素都是独一无二的。
  • 1 <= target <= 500

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

没有重复元素,数字可以无限制重复使用,数组并不是有序的

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
class Solution {
List<List<Integer>> res = null;
int[] candidates;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
res = new LinkedList<>();
if (candidates!=null&&candidates.length>0) {
LinkedList<Integer> choose = new LinkedList<>();
this.candidates = candidates;
dfs(choose,target,0);
}
return res;
}

void dfs(LinkedList<Integer> choose,int target,int index){
if (target==0) {
res.add(new LinkedList(choose));
return;
}

for (int i = index; i < candidates.length; i++) {
//剪枝
if (target>=candidates[i]) {
choose.add(candidates[i]);
//传递的是i,说明这个数字和可以使用
dfs(choose,target-candidates[i],i);
choose.pollLast();
}
}
}
}

组合

题目描述

给定一个数组 candidates 和一个目标数 target,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:

  • 所有数字(包括目标数)都是正整数。
  • 解集不能包含重复的组合。

示例 1:

1
2
3
4
5
6
7
8
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]

示例 2:

1
2
3
4
5
6
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

没有重复元素,数字不可以无限制重复使用,因为有重复元素,所以我们采用的方法是直接进行排序

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
class Solution {
List<List<Integer>> res = null;
int[] candidates;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
res = new LinkedList<>();
if (candidates!=null&&candidates.length>0) {
LinkedList<Integer> choose = new LinkedList<>();
Arrays.sort(candidates);
this.candidates = candidates;

dfs(choose,target,0);
}
return res;
}

void dfs(LinkedList<Integer> choose,int target,int index){
if (target==0) {
res.add(new LinkedList(choose));
return;
}

for (int i = index; i < candidates.length; i++) {
//大于 index 这个很灵性,需要和大于 0 进行对比
//大于 index说明index本身是可以取的
if (i>index && candidates[i]==candidates[i-1]) {
continue;
}

if (target>=candidates[i]) {
choose.add(candidates[i]);
//这里是i+1
dfs(choose,target-candidates[i],i+1);
choose.pollLast();
}else{
break;
}
}
}
}

总结

排列组合问题我总是忘记,所以记录一下,方便下次查看

旋转排序数组小结

在刷LeetCode的时候遇到了一些关于旋转排序数组的问题,记录一下。

33. 搜索旋转排序数组

题目描述

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

示例 1:

1
2
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4

示例 2:

1
2
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-in-rotated-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

二分法思路比较简单,因为做过很多次了

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
class Solution {
public int search(int[] nums, int target) {
if (nums==null||nums.length==0) {
return -1;
}
int left = 0;
int right = nums.length-1;
int mid = 0;

//使用二分法解决
//这里是<=号,为了防止nums.length=1的情况
while (left<=right) {
mid = (left+right)/2;
if(nums[mid]==target)return mid;
//这里也是<=号,为了处理类似于left=right=nums.length的情况
//此时mid=nums.length,如果这路不是<=,就会进入下面nums[mid+1]进行判断,可能出现越界
if (nums[left]<=nums[mid]) {
//说明 从 left到mid都为有序
if (nums[left]<=target && nums[mid]>target) {
//说明 target在left到mid
right = mid-1;
} else {
//说明 target在left到mid
left = mid+1;
}
}else{
//说明 从 mid+1到right为有序
if (nums[mid+1]<=target && nums[right]>=target) {
//说明在 mid+1到right
left = mid+1;
} else {
right = mid-1;
}
}
}
return -1;
}
}

81.搜索旋转排序数组II

题目描述

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。

编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。

示例 1:

1
2
输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true

示例 2:

1
2
3
输入: nums = [2,5,6,0,0,1,2], target = 3
输出: false
进阶:

这是 搜索旋转排序数组 的延伸题目,本题中的 nums 可能包含重复元素。
这会影响到程序的时间复杂度吗?会有怎样的影响,为什么?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

因为在这种情况下出现了元素重复的情况,我们可以把问题分为三类,转载自https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/solution/zai-javazhong-ji-bai-liao-100de-yong-hu-by-reedfan/

  • 第一类
    101111111101这种。此种情况下 nums[start] == nums[mid],分不清到底是前面有序还是后面有序,此时 start++ 即可。相当于去掉一个重复的干扰项。
  • 第二类
    22 33 44 55 66 77 11这种,也就是 nums[start] < nums[mid]。此例子中就是 2 < 5;
    这种情况下,前半部分有序。因此如果 nums[start] <=target<nums[mid],则在前半部分找,否则去后半部分找。和上面那题相同
  • 第三类
    66 77 11 22 33 44 55 这种,也就是 nums[start] > nums[mid]。此例子中就是 6 > 2;
    这种情况下,后半部分有序。因此如果 nums[mid] <target<=nums[end]。则在后半部分找,否则去前半部分找。和上面那题相同

大致代码和上面相同,但是出现一个判断,当然这题特殊情况下并不能实现完全的二分,最差的情况还是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
33
34
35
36
37
38
39
40
41
42
class Solution {
public boolean search(int[] nums, int target) {
if (nums==null||nums.length==0) {
return false;
}
int left = 0;
int right = nums.length-1;
int mid = 0;

//使用二分法解决
while (left<=right) {
mid = (left+right)/2;
if(nums[mid]==target)return true;
//多这个判断,将非选择答案去掉
if (nums[left] == nums[mid]) {
left++;
continue;
}

if (nums[left]<nums[mid]) {
//说明 从 left到mid都为有序
if (nums[left]<=target && nums[mid]>target) {
//说明 target在left到mid
right = mid-1;
} else {
//说明 target在left到mid
left = mid+1;
}

}else{
//说明 从 mid+1到right为有序
if (nums[mid+1]<=target && nums[right]>=target) {
//说明在 mid+1到right
left = mid+1;
} else {
right = mid-1;
}
}
}
return false;
}
}

153.寻找旋转排序数组中的最小值

题目描述

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请找出其中最小的元素。

你可以假设数组中不存在重复元素。

示例 1:

1
2
输入: [3,4,5,1,2]
输出: 1

示例 2:

1
2
输入: [4,5,6,7,0,1,2]
输出: 0

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

按照我的思路可以这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public int findMin(int[] nums) {
if (nums.length == 1) {
return nums[0];
}
int left = 0;
int right = nums.length - 1;
int mid = 0;
int minNum = Integer.MAX_VALUE;
while (left <= right) {
mid = (left+right)/2;
if (nums[left] <= nums[mid]) {
// 说明从 left 到 mid为有序
minNum = Math.min(minNum, nums[left]);
left = mid + 1;
} else {
// 说明从 mid 到 right为有序
minNum = Math.min(minNum, nums[mid]);
right = mid-1;
}
}
return minNum;
}
}

但是其实这题就是旋转旋转的最小值,其实也可以理解为寻找乱序的一边,因为乱序的一边里面肯定有最小值,但是这里面有许多小细节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int findMin(int[] nums) {
int left = 0;
int right = nums.length - 1;
int mid = 0;
//这里是 < 号,因为在nums.length==1的情况下
while (left < right) {
mid = (left + right) / 2;
//注意这路是拿 mid 和 right进行比较,后面会有解释
if (nums[mid] < nums[right]) {
// 说明从 mid 到 right为有序
right = mid;
} else {
// 说明从 left 到 mid为有序
left = mid+1;
}
}
return nums[left];
}
}

上面我们看到的细节是拿 mid 和 right进行比较这个其实也是有原因的。我们主要拿left,mid,right进行比较,会出现几种情况:

  1. 如果未进行旋转,整个数组有序。nums[left]<nums[mid]<nums[right]。此时是都有序,最小值在最左边,可以收缩右边界
  2. 进行了旋转,如果nums[left]>nums[mid],nums[mid]<nums[right],那说明左半部分是无序的,右半部分是有序的,那最小值肯定在左半部分,所有应该收缩右边界。eg:[8,9,1,2,3,4,5,6,7]这种情况
  3. 进行搜索,如果nums[left]<nums[mid],nums[mid]>nums[right],说明左半部分是有序的,右半部分是无序的,那最小值在右半部分,左边界应该收缩。eg:[4,5,6,7,8,9,1,2,3]

在这几种情况当中,我们发现如果使用left和mid进行比较的话,是分不出1,3这两种情况,而我们使用mid和right进行比较的话会发现1,2变成了一种情况,能够比较好的区分出来。所有使用mid 和 right进行比较。

还有一点有疑惑的地方就是为啥让right = mid为什么不是mid-1。其实这个是为了防止出现类似于[3,1,2]这种情况,因为刚好mid到right就是刚刚好旋转的部分,这样我们保留住mid就可以让左半边确保一定是无序的。

另外需要说明的是因为left不可能等于right,所以left<=mid<right,right=mid和left=mid+1是一定会让区间发生变化。

154.寻找旋转排序数组中的最小值

题目描述

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请找出其中最小的元素。

注意数组中可能存在重复的元素。

示例 1:

1
2
输入: [1,3,5]
输出: 1

示例 2:

1
2
输入: [2,2,2,0,1]
输出: 0

说明:

  • 这道题是 寻找旋转排序数组中的最小值 的延伸题目。
  • 允许重复会影响算法的时间复杂度吗?会如何影响,为什么?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

思路上承接上题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int findMin(int[] nums) {
if (nums.length == 1) {
return nums[0];
}
int left = 0;
int right = nums.length - 1;
int mid = 0;
while (left < right) {
mid = (left + right) / 2;
if (nums[mid] < nums[right]) {
// 说明从 mid+1 到 right为有序
right = mid;
} else if(nums[mid]>nums[right]){
// 说明从 left 到 mid为有序
left = mid+1;
}else{
right--;
}
}
return nums[left];
}
}

多了一种情况nums[mid]== nums[right],这个类似于81题的思路,可能出现101111111101这两种情况,因为无法确定我们只能让righ—,让它变成153题。

总结

这些旋转数组典型的特征就是二分搜索。发现了二分搜索几个比较重要的点:

  • 一般是比较left,mid,right三个位置的数然后再想办法收缩区间,一般来说每次都收缩一半。
  • 退出条件是while (left < right)还是while (left <=right)其实差别一般来说不大,
    • 当退出条件是while (left < right)时,说明退出条件为left==right,所以如果是类似搜索target之类的题目,最后比较一下left就行,如果是找位置则直接返回left就可以。
    • 当退出条件是while (left <=right)时,退出条件只有left>rightleft==right会在里面进行比较,所以一般来说答案在while循环里面就进行了比较,不需要出来再进行什么处理了。
  • 是比较nums[left]和nums[mid]还是比较nums[mid]和nums[right]需要推敲,但是一般情况下是不影响
  • left或者right其中之一在比较后一定需要进行数值上的更改(mid+1或者mid-1),不然可能陷入死循环。
    • 当退出条件是left<right时,left<=mid<right,所以righ需要更改t时,可以赋值为mid,但是left更改必须为mid+1
    • 当退出条件是left<=right时,left<=mid<=right,所以righ需要更改t时不能赋值为mid,必须为mid-1,而left必须为mid+1;
  • 当退出条件是left<=right时,并且需要比较nums[mid+1]和nums[right]时,需要注意mid+1可能会越界的问题

参考:https://leetcode-cn.com/problems/search-insert-position/solution/te-bie-hao-yong-de-er-fen-cha-fa-fa-mo-ban-python-/

替换空格

题目描述

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为“We Are Happy”.则经过替换之后的字符串为“We%20Are%20Happy“。

解题思路

其实这题比较简单,只需要对应好位置,就可以得出答案,时间上最小应该就是o(n)。但是情况上有两种可能,需要进行说明一下。

  • 能在原来的字符串数组上进行改动。能在原来数组上进行改动,那么只要找好最终数组的长度,然后从后向前找对应的位置就行。(为什么从后向前?因为从前向后,前面的遍历的就改变后面遍历得情况,而从后向前则不会)

    如:原来字符串为”We Are Happy“,长度为12(在java下没有考虑末尾的‘\0‘,如果是c等可能需要考虑),有两个空格,则最终的长度为14。然后我设p1=11,p2=13,然后如下:

    • array[p1]!=’ ‘,array[p2] = array[p1] ,接着(p2—,p1—)
    • array[p1]==’ ‘,array[p2]=’0’,array[p2-1]=’2’,array[p2-2]=’%’,接着(p2-=3,p—)
  • 不能在原来得数组上进行改动。不能在原来数组上进行改动,那么直接new一个最终长度得数组,然后从前向后对原数组遍历,确定位置就行。

    如:原来字符串为”We Are Happy“,长度为12(在java下没有考虑末尾的‘\0‘,如果是c等可能需要考虑),有两个空格,则最终的长度为14。我重新创建一个长度为14字符数组,设p1=0,p2=0,然后如下:

    • array[p1]!=’ ‘,Newarray[p2] = array[p1] ,接着(p2++,p1++)
    • array[p1]==’ ‘,Newarray[p2]=’%’,array[p2+1]=’2’,array[p2+2]=’%’,接着(p2+=3,p++)

代码实现

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
public class Solution {
public String replaceSpace(StringBuffer str) {
if(str==null)
return null;
int fianl_length = str.length();
for(int i=0;i<str.length();i++){
if(str.charAt(i)==' '){
fianl_length+=2;
}
}
char [] res = new char[fianl_length];
int p1 =0;
for(int i=0;i<str.length();i++){
if(str.charAt(i)==' '){
res[p1]='%';
res[p1+1]='2';
res[p1+2]='0';
p1+=3;
}else{
res[p1] = str.charAt(i);
++p1;
}
}
return String.valueOf(res);
}
}

我采用的是第二种方法,因为给的是StringBuffer,也不像字符串数组,所以采用第二种了。

总结

这是《剑指offer》上的一题,这题比较简单,无需多说。