Week 04 - Leetcode 31 - 40
31 - 下一个排列
+ 这题真的是写一次忘一次实际上本题是模拟一个C++中的一个库函数 next_permutation, 用法和sort差不多; 定义在头文件algorithm中,有next_permutation 和 prev_permutation两种; 如果存在a之后的排列,就返回true。如果a是最后一个排列没有后继,返回false,每执行一次,a就变成它的后继;
class Solution {
public:
void nextPermutation(vector<int>& nums) {
next_permutation(nums.begin(), nums.end());
}
};当然这么写肯定会被打了(逃
实际上,从后往前,找到第一个非降序的位置,找到第一个比当前元素大的元素并交换。
找下一个排列就是从后往前寻找第一个出现降的地方,把这个地方的数字与后边某个比它大的的数字交换,再把该位置之后整理为升序。
否则从数组末尾往前找,找到 第一个 位置j,使得 nums[j] < nums[j + 1]。
如果不存在这样的 j,则说明数组是不递增的,直接将数组逆转即可。
如果存在这样的 j,则从末尾找到第一个位置 i > j,使得 nums[i] > nums[j]。
交换 nums[i] 与 nums[j],然后将数组从 j + 1 到末尾部分逆转
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int k = nums.size() - 1;
// 1. 找到第一个递减点 e,g, 1,2,3 -> 1,3,2
while(k > 0 && nums[k - 1] >= nums[k]) k--;
if(k == 0) reverse(nums.begin(), nums.end()); // 如果降序排列 直接反转
else
{
// 2. 找到 k - 1 右边第一个比nums[k - 1]大的元素
int i = nums.size() - 1;
while(i > k && nums[i] <= nums[k - 1]) i--;
swap(nums[k - 1], nums[i]);
// 交换完之后仍然是降序(字典序最大) 转换为升序(字典序最小)
reverse(nums.begin() + k, nums.end());
}
}
};其中,找右边第一个比nums[k - 1]大的元素可以用什么优化 -> 二分
二分优化版
32 - 最长有效括号
有这样一个假设
标记任何不满足
s[1..i]中左括号数量>=右括号数量的位置,所有有效括号序列都不会经过这些位置
证明:反证法↓ 
所以整个序列被分成了不同的段,每一段都一定满足左括号的数量>=右括号的数量
在每一段内 用栈来对应左右括号 记录最大长度
如果栈为空,则证明这是一个不合法位置,更新start(即为开始下一段)
33 - 搜索旋转排序数组
二分一把梭 
对于nums仅有一个元素的情况而言,后面有可能出现l = 1但是r = 0的情况的; 所以最后的提交要写 nums[r]而不是nums[l];
最后下面刚好用了两种不同的二分模板,边界问题记住 有加就有减 就好了
34 - 在排序数组中查找元素的第一个和最后一个位置
二分一把梭
35 - 搜索插入位置
基础二分,唯一注意一下二分到右端点的时候, 如果nums[l] < target的话,l++才是最终插入的位置;
36 - 有效的数独
模拟题 记录行 列 九宫格中是否有元素出现过就好了
37 - 解数独
还是很经典的DFS 回溯 + 状态压缩 + 剪枝策略; 复习一下DFS的剪枝策略:
状态压缩用一个九位的二进制表示可以填数的位置,1表示该为可行 0 表示不行; 可以通过位运算直接得到同时符合行, 列, 九宫格要求的候选方案, 然后搜索就可以了; 搜索的时候在所有剩余没填的地方中找到扩展方案数最小的地方搜索(剪枝策略:优化搜索顺序)
ones[M]记录 一个二进制数中有几个一,power_2 记录log运算;
38 - 外观数列
就嗯模拟, 记得用stringstream;
39 - 组合总和
DFS就好了
40 - 组合总和II
和上一题差不多 多了一个条件:candidates 中的每个数字在每个组合中只能使用一次。 上一题:
不用 -> si 不变 // 用 -> si + 1
这一题: 额外记录重复元素出现多少次 用的话还是si + 1 但是不用这个元素的话 就要过掉所有同样的元素避免重复
Last updated