【LeetCode】 31 - 40题解
31. 下一个排列
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3
→ 1,3,2
3,2,1
→ 1,2,3
1,1,5
→ 1,5,1
public void nextPermutation(int[] nums) {
int i = nums.length - 2;
//找到第一个不再递增的位置
while (i >= 0 && nums[i + 1] <= nums[i]) {
i--;
}
//如果到了最左边,就直接倒置输出
if (i < 0) {
reverse(nums, 0);
return;
}
//找到刚好大于 nums[i]的位置
int j = nums.length - 1;
while (j >= 0 && nums[j] <= nums[i]) {
j--;
}
//交换
swap(nums, i, j);
//利用倒置进行排序
reverse(nums, i + 1);
}
private void swap(int[] nums, int i, int j) {
int temp = nums[j];
nums[j] = nums[i];
nums[i] = temp;
}
private void reverse(int[] nums, int start) {
int i = start, j = nums.length - 1;
while (i < j) {
swap(nums, i, j);
i++;
j--;
}
}
32. 最长有效括号
给定一个只包含 '('
和 ')'
的字符串,找出最长的包含有效括号的子串的长度。
示例 1:
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
示例 2:
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"
//")()(" --> 1001
public int longestValidParentheses(String s) {
Stack<Integer> stack = new Stack<>();
int n = s.length();
char[] chs = s.toCharArray();
int[] mark = new int[n];
int left = 0, len = 0, ans = 0;
for(int i = 0; i < n; i ++){
if(chs[i] == '(') stack.push(i);
else{
//没有匹配的左括号的右括号,置1
if(stack.isEmpty())mark[i] = 1;
//如果有匹配的左括号,弹出
else stack.pop();
}
}
// System.out.println(Arrays.toString(mark));
//多余的左括号,置1
while(!stack.isEmpty()){
mark[stack.pop()] = 1;
}
//找到最长连续0的长度
for(int i = 0; i < n; i ++){
if(mark[i] == 1){
len = 0;
continue;
}
len ++;
ans = Math.max(ans , len);
}
return ans;
}
33. 搜索旋转排序数组
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7]
可能变为 [4,5,6,7,0,1,2]
)。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1
。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
public int search(int[] nums, int target) {
if(nums.length == 0) return -1;
int l = 0, r = nums.length -1;
//找到最小值的索引位置
while(l < r){
int mid = l + r >> 1;
if( nums[mid] <= nums[nums.length -1 ])
r = mid;
else
l = mid + 1;
}//此时l == r == k
if( target <= nums[nums.length -1])
//[k,len-1]
r = nums.length -1;
else {
//左边那段中寻找[0,k-1]
l = 0;
r --; //因为上面二分完之后找到了第二段的第一个索引,因此r = r-1
}
while(l < r){
int mid = l + r >> 1;
if( nums[mid] >= target) r = mid;
else l = mid + 1;
}
//二分得出的是第一个>=target的值,需要判断一下是否就是寻找的那个数
if( nums[l] == target) return l;
return -1;
}
34. 在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums
,和一个目标值 target
。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]
。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
public int[] searchRange(int[] nums, int t) {
if(nums.length == 0) return new int[]{-1, -1};
int l = 0, r = nums.length -1;
//找第一个数
while(l < r){
int mid = l + r >>> 1;
//假设mid位置为8,前面可能还有,r = mid
if(nums[mid] >= t){
r = mid;
}else{
l = mid + 1;
}
}
//将会二分出大于等于t的第一个数如果不等于t 则不存在
if(nums[r] != t) return new int[]{-1, -1};
int start = r;
l = 0;
r = nums.length -1;
while(l < r){
//这里条件 l = mid, 注意这里向上取整
int mid = l + r + 1 >>> 1;
//找到第一个 <= target的数
if( nums[mid] <= t){
l = mid;
}else{
r = mid - 1;
}
}
int end = r;
return new int[]{start, end};
}
35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
public int searchInsert(int[] nums, int target) {
if(nums.length == 0 || nums[nums.length-1] < target) return nums.length;
int l = 0, r = nums.length -1;
//找到 >= target的第一个坐标
while( l < r){
int mid = l + r >> 1;
if( nums[mid] >= target){
r = mid;
}else{
l = mid + 1;
}
}
return r;
}
36. 有效的数独
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。
上图是一个部分填充的有效的数独。
数独部分空格内已填入了数字,空白格用 '.'
表示。
示例 2:
输入:
[
["8","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
说明:
- 一个有效的数独(部分已被填充)不一定是可解的。
- 只需要根据以上规则,验证已经填入的数字是否有效即可。
- 给定数独序列只包含数字
1-9
和字符'.'
。 - 给定数独永远是
9x9
形式的。
public boolean isValidSudoku(char[][] board) {
// 记录某行,某位数字是否已经被摆放
boolean[][] row = new boolean[9][9];
// 记录某列,某位数字是否已经被摆放
boolean[][] col = new boolean[9][9];
// 记录某 3x3 宫格内,某位数字是否已经被摆放
boolean[][] block = new boolean[9][9];
for(int i = 0; i < 9; i ++){
for(int j = 0; j < 9; j ++){
//.的情况不用考虑
if(board[i][j] == '.') continue;
//数字为1-9
int num = board[i][j] - '1';
//映射到子数独中
int index = i / 3 * 3 + j / 3;
if(row[i][num] || col[j][num] || block[index][num])
return false;
else{
row[i][num] = true;
col[j][num] = true;
block[index][num] = true;
}
}
}
return true;
}
37. 解数独
编写一个程序,通过填充空格来解决数独问题。
一个数独的解法需遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。
空白格用 '.'
表示。
一个数独。
答案被标成红色。
提示:
- 给定的数独序列只包含数字
1-9
和字符'.'
。 - 你可以假设给定的数独只有唯一解。
- 给定数独永远是
9x9
形式的。
public void solveSudoku(char[][] board) {
boolean flag = dfs(board, 0 ,0);
return;
}
boolean dfs(char[][] board, int i , int j){
if(i == 9) return true;
//到最后一列,换下一行
if(j == 9) return dfs(board, i + 1, 0);
if(board[i][j] != '.') return dfs(board, i , j + 1);
//选择列表1 - 9
for(char c = '1'; c <= '9'; c ++){
if (!isValid(board, i ,j ,c)) continue;
//选择
board[i][j] = c;
//下一层
if (dfs(board, i, j + 1)) return true;
//撤销选择
board[i][j] = '.';
}
return false;
}
boolean isValid(char[][] board , int x , int y ,char n){
for (int i = 0;i < 9; i ++){
//列重复
if (board[i][y] == n) return false;
//行重复
if (board[x][i] == n) return false;
}
//找到九宫格左上元素,依次遍历
for (int i = x / 3 * 3; i < x / 3 * 3 + 3; i ++){
for (int j = y / 3 * 3;j < y / 3 * 3 + 3; j ++){
if (board[i][j] == n) return false;
}
}
return true;
}
38. 外观数列
给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。
注意:整数序列中的每一项将表示为一个字符串。
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下:
1. 1
2. 11
3. 21
4. 1211
5. 111221
第一项是数字 1
描述前一项,这个数是 1
即 “一个 1 ”,记作 11
描述前一项,这个数是 11
即 “两个 1 ” ,记作 21
描述前一项,这个数是 21
即 “一个 2 一个 1 ” ,记作 1211
描述前一项,这个数是 1211
即 “一个 1 一个 2 两个 1 ” ,记作 111221
示例 1:
输入: 1
输出: "1"
解释:这是一个基本样例。
示例 2:
输入: 4
输出: "1211"
解释:当 n = 3 时,序列是 "21",其中我们有 "2" 和 "1" 两组,"2" 可以读作 "12",也就是出现频次 = 1 而 值 = 2;类似 "1" 可以读作 "11"。所以答案是 "12" 和 "11" 组合在一起,也就是 "1211"。
public String countAndSay(int n) {
String s = "1";
//循环n - 1次
for(int i = 0; i < n - 1 ; i ++){
StringBuilder sb = new StringBuilder();
//每一次记录相同一段的个数
for(int j = 0; j < s.length(); j ++){
int k = j;
while(k < s.length() && s.charAt(k) == s.charAt(j)){
k++;
}
//个数
String count = k - j + "";
//个数 + 字符
sb.append(count + s.charAt(j));
j = k - 1;
}
s= sb.toString();
}
return s;
}
39. 组合总和
给定一个无重复元素的数组 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的数字可以无限制重复被选取。
说明:
- 所有数字(包括
target
)都是正整数。 - 解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
https://www.cnblogs.com/summerday152/p/13615904.html
public List<List<Integer>> combinationSum(int[] arr, int t) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
Arrays.sort(arr);//排序是剪枝的前提
dfs(res,path,0,arr,t);
return res;
}
void dfs(List<List<Integer>> res,List<Integer> path,int s,int[] arr, int t){
if(t <= 0){
if(t == 0){
res.add(new ArrayList<>(path));
}
return;
}
for(int i = s; i < arr.length; i++){
if(arr[i] > t){ //由于数组已经有序,当前这个数应该小于等于剩余数t
break;
}
path.add(arr[i]);
dfs(res,path,i,arr,t-arr[i]); //因为
path.remove(path.size()-1);
}
}
40. 组合总和 II
https://www.cnblogs.com/summerday152/p/13615904.html
给定一个数组 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的每个数字在每个组合中只能使用一次。
说明:
- 所有数字(包括目标数)都是正整数。
- 解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
public List<List<Integer>> combinationSum2(int[] arr, int t) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
Arrays.sort(arr);//排序是剪枝的前提
dfs(res,path,0,arr,t);
return res;
}
// s 可以看成层数, i可以看成这一层从第几个开始
void dfs(List<List<Integer>> res,List<Integer> path,int s,int[] arr, int t){
if(t <= 0){
if(t == 0){
res.add(new ArrayList<>(path));
}
return;
}
for(int i = s; i < arr.length; i++){
if(arr[i] > t){ //由于数组已经有序,当前这个数应该小于等于剩余数t
break;
}
//保证递归树的同一个层级不出现相同的元素
if(i > s && arr[i] == arr[i - 1]) continue;
path.add(arr[i]);
dfs(res,path,i+1,arr,t-arr[i]);
path.remove(path.size()-1);
}
}