31. 下一个排列
从右向左找到第一个数字不再递增的位置设为nums[i],然后在nums[i]右边找到一个刚好大于当前位的数字nums[j]交换;
然后将nums[i]右边的数字sort一下;
由于从右向左是递增的,所以可以写一个倒序函数来sort,或者直接用Arrays.sort也行
//有一点单调栈的思想在里面
//我们其实是从右向左找到第一个数字不再递增的位置,然后从右边找到一个刚好大于当前位的数字即可。
public static void nextPermutation(int[] nums) {
int i = nums.length - 2;
//从右向左找到第一个数字不再递增的位置
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
//如果从右向左一直递增
if (i < 0) {
Arrays.sort(nums);
return;
}
int j = nums.length - 1;
//从右边找到一个刚好大于当前位的数字
while (j >= 0 && nums[j] <= nums[i]) {
j--;
}
swap(nums, i, j);
int left = i + 1;
int right = nums.length - 1;
while (left < right) {
swap(nums, left, right);
left++;
right--;
}
return;
}
public static void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
32. 最长有效括号
- 从左到右扫描字符串,扫描到左括号,就将当前位置入栈。
- 扫描到右括号,就将栈顶出栈(代表栈顶的左括号匹配到了右括号),这个时候然后分一下两种情况:
- 栈不空,那么就用当前的位置减去栈顶的存的位置,然后就得到当前合法序列的长度,然后更新一下最长长度。
- 栈是空的,说明之前没有与之匹配的左括号,那么就将当前的位置入栈
- 其实多余的左括号和右括号的位置,都会落到栈底,为什么?因为合法的括号都是成对出现的,左括号都会压入的话遇见右括号就弹出了,最终不会出现在栈里面
- 初始化的时候为了便于计算第一个合法括号(我们需要pop之后的peek),压入-1
public int longestValidParentheses(String s) {
if (s == null || s.length() == 0) return 0;
Stack<Integer> stack = new Stack<>();
//为了方便计算第一个合法括号的子串长度,我们先加一个-1
stack.push(-1);
int res = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
//扫描到左括号,就将当前位置入栈
if (c == '(') {
stack.push(i);
} else {
//扫描到右括号,先pop
stack.pop();
//如果栈空,说明之前没有左括号与之匹配,则将当前位置压栈
if (stack.isEmpty()) {
stack.push(i);
} else {
//如果栈不是空的,就用i-stack.peek()更新res
res = Math.max(res, i - stack.peek());
}
}
}
return res;
}
33. 搜索旋转排序数组
- 查找一般都是用二分,题目也要求时间复杂度对数级别
- 用闭区间表示[0,nums.length - 1],所以跳出循环的时候需要left>right(这样才是空集)
- 若nums[left] <= nums[mid],说明mid前面是有序的:
- nums[left] <= target < nums[mid]时,在前半部分找,right=mid-1;
- 否则在后半部分找,left=mid+1;
- 同理, 若nums[left] > nums[mid],说明mid后面是有序的
- nums[mid] < target <= nums[right]时,在后半部分找left=mid+1
- 否则,right=mid-1;
public static int search2(int[] nums, int target) {
if (nums == null || nums.length == 0) return -1;
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = (left + right) >>> 1;
if (nums[mid] == target) {
return mid;
}
//mid前面都是有序的,nums[left]<=target<nums[mid],则在前半部分找,否则去后半部分找。
if (nums[left] <= nums[mid]) {
if (nums[left] <= target && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;//注意这里不能写成left = mid;因为已经判断过了nums[mid] != target
}
} else {
if (target > nums[mid] && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
34. 在排序数组中查找元素的第一个和最后一个位置(经典)
- 找左边界left的时候,一下几种情况直接返回不存在:
- 如果target < nums[0],i=0;
- 如果target > nums[nums.length-1],i = nums.length;
- 如果nums[0]<target<nums[nums.length-1]但是nums[i] != target
- 若左边界left存在,那么右边界肯定存在, 且left<=right<=nums.length-1
- j指向right
public int[] searchRange(int[] nums, int target) {
if (nums == null || nums.length == 0) return new int[]{-1, -1};
int i = 0;
int j = nums.length - 1;
//找左边界引索
while (i <= j) {
int mid = (i + j) >>> 1;
if (nums[mid] == target) {
j = mid - 1;
} else if (nums[mid] < target) {
i = mid + 1;
} else if (nums[mid] > target) {
j = mid - 1;
}
}
//如果target < nums[0],i=0;如果target > nums[nums.length-1],i >= nums.length;
// 如果nums[0]<target<nums[nums.length-1]且nums[i] != target
if (i >= nums.length || i >= 0 && nums[i] != target) {
return new int[]{-1, -1};
}
int left = i;
i = 0;
j = nums.length - 1;
//找右边界引索
while (i <= j) {
int mid = (i + j) >>> 1;
if (nums[mid] == target) {
i = mid + 1;
} else if (nums[mid] < target) {
i = mid + 1;
} else if (nums[mid] > target) {
j = mid - 1;
}
}
int right = j;
return new int[]{left, right};
}
35. 搜索插入位置
public int searchInsert(int[] nums, int target) {
int length=nums.length;
if(length==0) return 0;
//插入位置有可能在数组的末尾
if(target>nums[length-1]){
return length;
}
//如果target比nums[length-1]小,那插入的index只可能在[0,length-1]
int left=0;
int right=length-1;
while (left<right){//意思是left=right时返回任意一个即可
int mid=(left+right)>>>1;
if(nums[mid]<target){//不可能
left=mid+1;
}else{//有可能
right=mid;
}
}
return left;
}