题目一 :二分查找和 278. 第一个错误的版本

注意边界的变化方式。一般是
high = mid + 1
但有的时候可能没有 +1,甚至有 -1.

题目二:35. 搜索插入位置

二分法难点就在于开闭的决定。这题就把我难住了,主要是不知道 mid 是否 + 1。

先看看别人的

有个参考

以这道题目来举例,以下的代码中定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要)。
这就决定了这个二分法的代码如何去写,大家看如下代码:
大家要仔细看注释,思考为什么要写while(left <= right), 为什么要写right = middle - 1。

class Solution {public:int searchInsert(vector<int>& nums, int target) {int n = nums.size();int left = 0;int right = n - 1; // 定义target在左闭右闭的区间里,[left, right]while (left <= right) { // 当left==right,区间[left, right]依然有效int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2if (nums[middle] > target) {right = middle - 1; // target 在左区间,所以[left, middle - 1]} else if (nums[middle] < target) {left = middle + 1; // target 在右区间,所以[middle + 1, right]} else { // nums[middle] == targetreturn middle;}}// 分别处理如下四种情况// 目标值在数组所有元素之前  [0, -1]// 目标值等于数组中某一个元素  return middle;// 目标值插入数组中的位置 [left, right],return  right + 1// 目标值在数组所有元素之后的情况 [left, right],这是右闭区间,所以  return right + 1return right + 1;}
};

这是别人的代码,但是有个问题,他定义了“target在左闭右闭的区间里,[left, right]”,那你为什么最后返回的是“right + 1”?

再想想自己的

本质上是维护一个范围(当然还有数在范围之外的情况,另外讨论就好),关键是范围的正确缩小:就是你要插入的位置一定要在这个范围之内,所以当你调整范围的时候要慎重,别把正确的序号给排除了就好。
大概有这几种情况:

  1. 如果你摸到的数就是插入的数,那这个被摸到的数的序号就是结果;
  2. 如果你摸到的数小于目标,那么新来的数就不可能在 “这个数以及这个数之前了”,即 low = mid + 1(或者说新来的数不可能撼动这个较的小数的位置,只能往后排);
  3. 如果你摸到的数大于目标,新来的“小子”是可以占用你的序号的!所以 high = mid(没有 -1);

然后你再加上“数在范围之外的情况”,应该就好了。代码如下

    int searchInsert(vector<int>& nums, int target) {int  low = 0, high = nums.size() - 1;// 数在范围之外的情况if (target <= nums[low]) return 0;if (target > nums[high]) return high+1;while (low < high){//if (high - low == 1) return high;int mid = low + (high - low) / 2;if (nums[mid] > target) high = mid ;if (nums[mid] < target) low = mid + 1;if (nums[mid] == target) return mid;}return high ;}

在掌握了范围的正确缩小算法后,我们再来看看前面的题目。

对于题目 704. 二分查找

套用上面的思想,可以得到:

  1. 如果你摸到的数就是查找的数,直接返回;
  2. 如果你摸到的数小于目标,那么新来的数一定在这个数的后面,所以用 low = mid + 1;(这有个疑问,如果你用 low = mid 的话相当于放宽了下限,但整体范围也是会缩小的,也能起到作用。不这样做的原因是好像有一次我这么干了,但巧了当时 low = mid,导致了无限循环,所以缩小范围还是不能多也不能少)
  3. 如果你摸到的数大于目标,那么新来的数一定在这个数的前面!所以 high = mid - 1

对于题目 35. 搜索插入位置

套用上面的思想,可以得到:

  1. 如果你摸到的是ok,则结果绝对不会在此之后,可能是这个,也可能是这个前的,所以用 high = mid
  2. 如果是false,则结果绝对在此之后,不可能是这个,所以用 low = mid + 1

当二分法的思路清晰之后,代码就变得简洁了(从下面的代码一 -> 代码二)

// 代码一,罗里吧嗦,奇技淫巧,一通操作
int firstBadVersion1(int n) {if (n == 1) return 1;int low = 1, high = high = n;while (low < high){int mid = low + (high - low) / 2;if (isBadVersion(mid) == true){if (isBadVersion(mid - 1) == true){high = mid - 1;mid = low + (high - low) / 2;}else return mid;}else{if (isBadVersion(mid + 1) == true) return mid + 1;else{low = mid + 1;mid = low + (high - low) / 2;}}}return low;
}
// 代码二(简洁版)
int firstBadVersion3(int n) {if (n == 1) return 1;int low = 1, high = high = n;while (low < high){int mid = low + (high - low) / 2;if (isBadVersion(mid) == true) high = mid;//如果是ok,绝对不会在此之后,可能是这个,也可能是这个前的if (isBadVersion(mid) == false) low = mid + 1;}return low;
}

完工!


题目四、283. 移动零

双指针:一个快,一个慢。快的遇到0就pass,不是零就让慢指针写入。最后填充0就好。

    void moveZeroes(vector<int>& nums) {size_t index_fast = 0, index_slow=0;for (; index_fast < nums.size(); index_fast++){if (nums[index_fast] == 0) continue;nums[index_slow++] = nums[index_fast];}for (; index_slow < nums.size(); index_slow++) nums[index_slow] = 0;}

但是“填充0”其实不是原位操作,原位操作可以用“交换”。
其实一个是读指针,一个是写指针。写的不在乎位置上原来的数,读指针读取过后也不在乎原来的,所以当不为0的时候可以交换。只要保证写的都是非零就好。


void moveZeroes(vector<int>& nums) {int n = nums.size(), left = 0, right = 0;while (right < n) {if (nums[right]) {swap(nums[left], nums[right]);left++;}right++;}
}

题目五、167. 两数之和 II - 输入有序数组

有序查找用二分(有序 = 有树,二分查找 = 节点遍历)。我一开始想到的就是对每个数以及其后面的所有数进行二分查找

int search(vector<int>& nums, int target, int begin_index) {int left = begin_index, right = nums.size() - 1;while (left <= right) {int mid = (right - left) / 2 + left;int num = nums[mid];if (num == target) return mid;else if (num > target)   right = mid - 1;else   left = mid + 1;}return -1;
}vector<int> twoSum(vector<int>& numbers, int target) {vector<int> result(2);for (int i = 0; i < numbers.size(); i++){int index_ = search( numbers, target - numbers[i], i +1);if (index_ != -1){result[0] = i + 1;result[1] = index_ + 1;return result;}}return result;
}

看官方发现:头尾双指针法。我一开始不用是因为怕漏了,其实不会,因为它也是一个范围缩小问题,这种问题要保证缩小的恰好。

// 双指针 别人的
vector<int> twoSum(vector<int>& numbers, int target) {int i = 0;int j = numbers.size()-1;while (i < j) {int sum = numbers[i] + numbers[j];if (sum < target) {i++;} else if (sum > target) {j--;} else {return vector<int>{i+1, j+1};}}return vector<int>{-1, -1};
}作者:nettee
链接:https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/solution/yi-zhang-tu-gao-su-ni-on-de-shuang-zhi-zhen-jie-fa/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


#include<iostream>
#include<algorithm>//必要头文件
#include<vector>
#include<string>
using namespace std;
class TakeBus
{public:void TakeBusToSubway(){cout << "go to Subway--->please take bus of 318" << endl;}void TakeBusToStation(){cout << "go to Station--->pelase Take Bus of 306 or 915" << endl;}
};
//知道了去哪要做什么车可不行,我们还得知道有没有这个车
class Bus
{public:virtual void TakeBusToSomewhere(TakeBus& tb) = 0;  //???为什么要等于0
};class Subway :public Bus
{public:virtual void TakeBusToSomewhere(TakeBus& tb){tb.TakeBusToSubway();}
};
class Station :public Bus
{public:virtual void TakeBusToSomewhere(TakeBus& tb){tb.TakeBusToStation();}
};void reverseString_1( string & s, size_t start, size_t end) {size_t left = start, right = end;while (left < right){swap(s[left], s[right]);left++;right--;}
}
void reverseString(vector<char>& s) {size_t left = 0, right = s.size() - 1;while (left < right){swap(s[left], s[right]);left++;right--;}
}string reverseWords(string s) {size_t left = 0, right = left;for (size_t i = 0; i < s.size(); i++){if (s[i] != ' ') right++;if (s[i] == ' ' || i == max((int)s.size() - 1, 1)){reverse(s.begin()+left, s.begin()+ right );right++;left = right;}}return s;
}int main()
{int a[10] = { 1,5,6,3,9,2,8,7,4,10 };int n = 4;vector<char>  s ={ 'h','e','l','l','o' };reverseString(s);string str = "Let's take LeetCode contest";str = reverseWords(str);return 0;
}

滑动窗口相关

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

听着“滑动窗口”,其实也就是双指针的一种特例。所以大概思路就是范围的不断调整(加上后面的数,如果不重复就加上,重复就删去第一个)。这么想没问题但是有几个问题

  1. 如果判断某个string 是否有重复的数?
    我开始找了什么先 sort 然后 unique 的方法。但是忘了“重复”最好的方法就是哈希表法!
  2. 在当前循环想着自己的条件就好,别老是想下一个数是否符合啦之类的,不然容易出篓子。
  3. 一般只有循环变量用 size_t,外面的能用int就int,不然容易出错

下面是我的代码

int lengthOfLongestSubstring(string s) { // 在我看来,滑动窗口就是双指针unordered_set<char> set;int  left = 0, right = left;int max_ = 0;if (right == (int)(s.size() - 1)) return 1;if (right >= (int)(s.size() - 1)) return 0;  // right 不用 size_t 类型,用了有bug // 时间都浪费在是否重复上面的了, -》 维护一个当前窗口的哈希setwhile (right <= s.size() - 1){//if (set.find(s[right]) == set.end()) // 别老是想着下一个,想想自己(不要老是看right+1,而是看看right){            max_ = max(max_, (int)(right - left + 1));set.insert(s[right]);right++;}else{set.erase(s[left]);left++;}}return max_;
}

题目四、567. 字符串的排列

这一定是基于固定滑动窗口的题目,但是我的速度太慢了(1070ms)?
滑动窗口法是肯定的!但这题不建议用哈希,有限直方图用数组!‘
最初的思路:对比二者的哈希统计字典。但是创建字典、比较字典导致下面的代码运行时间过长

 bool CompareMap(  unordered_map<char, int>& map1,   unordered_map<char, int>& map2)
{if (map1.size() != map2.size()){return false;}for (pair<char, int> kv : map1) {if (map2[kv.first] != kv.second){return false;}}return true;
}void getdict(string s, int left, int right, unordered_map<char, int> & dict)
{for (int i = left; i <= right; i++){dict[s[i]] += 1;}
}bool checkInclusion(string s1, string s2) {unordered_map<char, int> dict_s1;for (auto s : s1){dict_s1[s] += 1;}unordered_map<char, int> dict_s2;int left = 0, right = left + s1.size() - 1;while (right <= s2.size() - 1){dict_s2.clear();if (dict_s1.find(s2[right]) != dict_s1.end()){getdict(s2,left, right, dict_s2);if (CompareMap(dict_s1, dict_s2) == false){left++; right++;}else{return true;}}else{left++; right++;}}return false;}

有人说:注意到每次窗口滑动时,只统计了一进一出两个字符,却比较了整个字典从这个角度出发,用一个变量diff 来记录比较好
于是有下面的思路:


class Solution {public:bool checkInclusion(string s1, string s2) {int n = s1.length(), m = s2.length();if (n > m) {return false;}vector<int> cnt(26);for (int i = 0; i < n; ++i) {--cnt[s1[i] - 'a'];++cnt[s2[i] - 'a'];}int diff = 0;for (int c : cnt) {if (c != 0) {++diff;}}if (diff == 0) {return true;}for (int i = n; i < m; ++i) {  // 其实这里不建议用哈希,因为假设你往前遇到一个新字母,你必须得插入他,还不如用数组好int x = s2[i] - 'a', y = s2[i - n] - 'a';if (x == y) {continue;}if (cnt[x] == 0) { //当前不需要你新来的数但是你非要加入,不同数+1,直方图+1++diff;}++cnt[x];//[加入的影响1]if (cnt[x] == 0) { //[加入的影响2] 加入后平了,则在这个数方面离成功又近了一步--diff;}if (cnt[y] == 0) {  //老的数非要走,走前这个方面已经成熟了,你走了就缺了++diff;}--cnt[y]; // 老的走if (cnt[y] == 0) {  --diff;}if (diff == 0) {return true;}}return false;}
};

双指针的注意 题目 27. 移除元素

这个题目思路对,但提交了多次都不行。其实是代码逻辑不清晰。
我的想法:

  1. 两个从后往前的指针,但是一个快一个慢。
  2. 如果快的发现了值,就和快的交换。

但是注意以下几点:

  1. 快慢指针初期最好相等(可以快比慢提前一步,但这就要求数组有2个元素)
  2. 指针最后变换,否则容易乱。
// 一开始的乱代码,还是错的
int removeElement2(vector<int>& nums, int val) {int slow = nums.size() - 1;if (nums.size() == 0) return 0;if (nums.size() == 1){if (nums[0] == val) return 0;else return 1;}while (nums[slow] == val && slow >= 0){slow--;if (slow < 0) return 0;}int fast = slow - 1; // 不要这样做, 快慢指针初期最好相等if (slow < 0 || fast < 0) return slow;while (1)   // 在进入循环前,保证下标正确{// 调整 放置新2 的地方 slowwhile (nums[slow] == val&& slow>=0) {slow--;if (slow < 0) return slow;}// 调整 leftif (nums[fast] == val){swap(nums[fast], nums[slow]);slow--;}if (fast == 0) break;fast--;}return slow + 1;
}
// 整齐的代码
int removeElement(vector<int>& nums, int val) {int slow = nums.size() - 1;for (int fast = slow; fast >= 0; fast--) // 双指针一般要求大于2个,但是如果这样的就不用了{if (nums[fast] == val){swap(nums[fast], nums[slow]);slow--;}}return slow+1;
}

题目 695. 岛屿的最大面积

没有什么技巧,注意:

  1. [实际操作]部分一定要写在一起,不然容易乱;
  2. 行列别偷懒,一定另外建立变量,不然自己容易晕;
int getMaxArea2( vector<vector<int>>& grid, int sr, int sc) // 为什么这么大?// 就算不用 passed 也还是这么大? - 官方更大 - 好像广度优先都是这样?
{int area = 0;queue<pair<int, int>> q;pair <int, int> point;vector<vector<int>>neibourhood = {{0,-1},{0,1},{-1,0},{1,0}};// --------  下面这三行要一起出现 ------------q.emplace(make_pair(sr, sc));grid[sr][sc] = 0;area++;// -------------------------------------------while (q.empty() == false){point = q.front();for (auto v : neibourhood){int new_row = point.first + v[0]; // 行列一定另外建立变量,不然自己容易晕int new_col = point.second + v[1];if (new_row >= grid.size() || new_row < 0 || new_col >= grid[0].size() || new_col < 0){continue;}if (grid[point.first + v[0]][point.second + v[1]] == 1){// --------  下面这三行要一起出现 ------------area += 1;q.emplace(make_pair(point.first + v[0], point.second + v[1]));grid[new_row][new_col] = 0;// ------------------------------------------}}q.pop();}return area;
}int maxAreaOfIsland2(vector<vector<int>>& grid) {int max_area = 0;//vector<vector<int>> passed(grid.size(), vector<int>(grid[0].size()));for (size_t i = 0; i < grid.size(); i++){for (size_t j = 0; j < grid[0].size(); j++){if (grid[i][j] == 1  ){max_area = max(max_area, getMaxArea2(grid,  (int)i, (int)j));}}}return max_area;
}int main()
{vector<vector<int>> image = { {1, 1, 1} ,{1, 1, 0},{1, 0, 1 } };//floodFill2(image, 1, 1, 2);vector<vector<int>> grid = {{0,0,1,0,0,0,0,1,0,0,0,0,0},{0,0,0,0,0,0,0,1,1,1,0,0,0},{0,1,1,0,1,0,0,0,0,0,0,0,0},{0,1,0,0,1,1,0,0,1,0,1,0,0},{0,1,0,0,1,1,0,0,1,1,1,0,0},{0,0,0,0,0,0,0,0,0,0,1,0,0},{0,0,0,0,0,0,0,1,1,1,0,0,0},{0,0,0,0,0,0,0,1,1,0,0,0,0},};vector<vector<int>> grid2 = {{1,1,0,0,0},{1,1,0,0,0},{0,0,0,1,1},{0,0,0,1,1},};vector<vector<int>> grid3 = {{0,0,0,0,0,0,0,0},};int i = maxAreaOfIsland2(grid3);}

题目 617. 合并二叉树

注意,指针,当你不对他所指进行操作,而只对它本身进行操作的时候,还是值引用。

void qiantao(TreeNode* & root1, TreeNode* & root2) {if (root1 == nullptr && root2 ==nullptr) return;if (root1 != nullptr && root2 == nullptr){root2 = root1;return;}if (root1 == nullptr) return;if (root1 != nullptr && root2 != nullptr){root2->val += root1->val;}qiantao(root1->left, root2->left);qiantao(root1->right, root2->right);
}TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {// 先从根节点节点看看qiantao(root1, root2);return root2;}

发现

  1. 解法有点怪怪的,用 void 函数返回了。但题目本意是让你返回节点来迭代的。
class Solution {public:TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {if (t1 == NULL) return t2; // 如果t1为空,合并之后就应该是t2if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1// 修改了t1的数值和结构t1->val += t2->val;                             // 中t1->left = mergeTrees(t1->left, t2->left);      // 左t1->right = mergeTrees(t1->right, t2->right);   // 右return t1;}
};

题目 542. 01 矩阵

我认为应该是遍历每个数,然后广度就好。但是一下问题

  1. 如何记录广度的深度?
    答:利用队列的长度就好!

所以更新老的算法流程,
老的广度搜索算法流程

  1. 放入出发点作为队列头
  2. 循环(如果列表为空则退出)
    3. 找队列头的下一步点,将所有可能的下一步点放入队列(排除:超边界、走过的路)
    4. 如果某个点等于终点结束大循环
    5. 当那个点的下一步点全部入队列后,删除那个点

新的广度搜索算法流程

  1. 放入出发点作为队列头;
  2. 循环(如果列表为空则退出)
    1. 记录当前队列大小(作为下一层的节点数量)并开始循环
      1. 循环的方式找队列头的下一步点,将所有可能的下一步点放入队列(排除:超边界、走过的路)
      2. 删除那个点
    2. 深度 +1

具体代码如下,但是超时了!主要是复杂度高!


int get_nestest_dist2(vector<vector<int>> mat, int sr, int sc, int target)
{queue<pair<int, int>> q;pair <int, int> point;vector<vector<int>>neibourhood = {{0,-1},{0,1},{-1,0},{1,0}};vector<vector<int>> passed(mat.size(), vector<int>(mat[0].size()));int step = 1;q.emplace(make_pair(sr, sc));passed[sr][sc] = 1;while (q.empty() == false){point = q.front();for (auto v : neibourhood){int new_row = point.first + v[0];int new_col = point.second + v[1];if (new_row >= mat.size() || new_row < 0 || new_col >= mat[0].size() || new_col < 0){continue;}if (passed[new_row][new_col] != 0) continue;if (mat[new_row][new_col] == 1){q.emplace(make_pair(new_row, new_col));passed[new_row][new_col] = 1;}else if (mat[new_row][new_col] == 0) return step;}q.pop();step++; // 有错误}return step;
}int get_nestest_dist(vector<vector<int>> mat, int sr, int sc, int target)
{queue<pair<int, int>> q;pair <int, int> point;vector<vector<int>>neibourhood = {{0,-1},{0,1},{-1,0},{1,0}};vector<vector<int>> passed(mat.size(), vector<int>(mat[0].size()));int step = 1;q.emplace(make_pair(sr, sc));passed[sr][sc] = 1;while (q.empty() == false){//point = q.front();int size = q.size(); // 记录当前队列大小// 遍历这一层所有节点for (int i = 0; i < size; i++){point = q.front();// 找这个节点的邻居for (auto v : neibourhood){int new_row = point.first + v[0];int new_col = point.second + v[1];if (new_row >= mat.size() || new_row < 0 || new_col >= mat[0].size() || new_col < 0)  continue;if (passed[new_row][new_col] != 0) continue;if (mat[new_row][new_col] == 1){q.emplace(make_pair(new_row, new_col));passed[new_row][new_col] = 1;}else if (mat[new_row][new_col] == 0) return step;}q.pop();}step++;}return step;}

其实广度遍历 = 树层序遍历,一般都是一个起点,但是这里是N个起点了,其他的都一样。
学习一下官方的代码


class Solution {private:int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public:vector<vector<int>> updateMatrix(vector<vector<int>>& matrix) {int m = matrix.size(), n = matrix[0].size();vector<vector<int>> dist(m, vector<int>(n));vector<vector<int>> visited(m, vector<int>(n));queue<pair<int, int>> q;// 将所有的 0 添加进初始队列中,一行一行遍历整个地图for (int i = 0; i < m; ++i) {for (int j = 0; j < n; ++j) {if (matrix[i][j] == 0) { q.emplace(i, j);    //将(x,y)坐标添加到队列中visited[i][j] = 1; //并标记等于0的坐标,方便第二次遍历}}}// 广度优先搜索,层序遍历,一行一行遍历while (!q.empty()) {auto [i, j] = q.front(); //获取队列首元素,提取当前坐标,遍历上下左右q.pop();    //出队//遍历当前坐标的上下左右for (int d = 0; d < 4; ++d) {int ni = i + dirs[d][0];int nj = j + dirs[d][1];//如果坐标符合当前,并且标记没有走过或者地图上等于0//visited[ni][nj]等于0表示原来的地图为1if (ni >= 0 && ni < m && nj >= 0 && nj < n && !visited[ni][nj]) {dist[ni][nj] = dist[i][j] + 1;      //更新坐标,和相邻的坐标+1q.emplace(ni, nj);  //加入队列,为下一个坐标做准备visited[ni][nj] = 1; //标记已经走过,防止重复进入死循环}}}return dist;        //返回更新后的地图}
};

问题

  1. 为什么是以0为中心,而不是以1?
  2. 好像不能是从1开始的广度遍历(我一开始就是从1开始的,毕竟是从1出发的)
    a. 因为一个起源就是一个矩阵,但是如果你以1开始扩散,你就会发现,当一团1窝在一起的时候,标记过的节点还可能再次更新标记,但如果从0开始扩散,标记过的就不会再修改了!
    b. 0 和 1 的地位不同,一窝0中中间的0没有任何作用,但是中间的1还是有作用的。

动态规划

如何找递推公式

步骤一、依据 70. 爬楼梯 题目来。

当n==1时,显然,方法数为f(1) == 1
当n= =2时,有两种情况:第一种是每次走一步,第二种是一次跨两步,所以方法数为f(2) = = 2
当n= =k时,方法数f(k)= =多少呢?我们不妨假设我们已知f(k-1),那么从第k-1个位置出发只需要再跨一步即可到达第k个位置,所以f(k)==f(k-1)?显然不对吧?上面f(1)已经不等于f(2)了,问题出在哪里呢?出在我们不应该在一开始认定“到达第k个位置必须经过第k-1个位置”。因为你是可以一次跨两步的,你可以从第k-2个位置直接到达第k个位置,而不需要经过第k-1个位置。由于到达第k-2个位置的方法数是f(k-2),所以到达第k个位置的方法数应为:f(k-1)+f(k-2)。

作者:hardcore-6angulywaf
链接:https://leetcode.cn/problems/climbing-stairs/solution/lc-70pa-lou-ti-du-chuang-de-er-fen-di-gu-zneb/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这里有个暗示:如果没有在k处下脚,一定下脚在k+1处。

步骤二、依据 198. 打家劫舍 题目来。

  1. 一定不要管后面的(后面的有人管了)
    按照上面的思路假定“我们已知f(k-1)”,然后呢?当前k是否要加呢?我这时会说分情况:a. 如果k-1有了,那么k不加入;b. 如果k+1的利润很丰富,那么k不加入… 没玩没了了。首先,后面的情况无论如何都超出子问题的定义了(可以这样想:你子问题的时候考虑后面的,总问题为什么不考虑?因为总问题就给了100个数据,没有个101个,所以不用考虑101个。那总问题不考虑,子问题就不需要考虑)
    所以排除b
  2. 不影响到k的,不归当前子问题管
    按照上面的思路继续,把当前子问题进行拆解。
    a. 当前k加入,则盗窃向量 [… , ? , 0, 1]
    b. 不加入 [ … ? , ?, 0]
    我任务第一个情况应没有什么问题,第二个情况我就多想:既然当前没有加入,那前一个加入的概率是否更大?回答:不用管,原因一:只要看拆解的情况是否齐全就好,一定要齐全。a+b 是齐全的,所以递推公式里面至少有2项
  3. 好,这个时候我们可以吧子问题进行数学表示。
    当前子问题其实就是[???…?] (k个问号) 等价于 f(k),而这个子问题 = [?.. , ? , 0, 1] + [ ?.. ? , ?, 0]。其中第二项的k-1个问号就是 f(k-1) ,第一项就是 f(k-2)+value(k) ,于是递推公式有了:

做完了再看看当时的疑问

当时疑问1:方案一中,你怎么知道k-2是否被偷?那还得分情况讨论
当时疑问2:方案二,k-2确实不偷,但是k-3呢?

上面这两个疑问就是你想多了。

总结一下,就是将子问题拆分为 子子问题,做不出来的原因是:你考虑的情况超出子问题的范围了(比如情况b中你还考虑了后面k+1的,方案二中你还考虑什么k-3)。为了防止多想,用向量表示选择,用问号表示所有可能性就好。

双暴力,按顺序。题目15. 三数之和

这个题目我是有思路的,但是在如何去除重复上面有点难。
注意:

  1. 双顺序,最好不要反着来,否则没有办法避免重复
// 我的,i 和 j 循环没有安装顺序来
vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> result;sort(nums.begin(), nums.end());for (int i = 1; i <= nums.size() - 2; i++){for (int j = 0; j < i; j++){if (j - 1 >= 0 && nums[j] == nums[j - 1]) continue;// 寻找索要的值int target = 0 - nums[i] - nums[j];for (int k = nums.size() - 1 ; k >= i + 1 ; k--){if ( k+1< nums.size() && nums[k] == nums[k+1]) continue;if (nums[k] < target) break;if (nums[k] == target){vector<int> v = { nums[i],nums[j], nums[k] };result.push_back(v);}}}}return result;  // 双顺序,最好不要反着来,否则没有办法避免重复
}
class Solution {public:vector<vector<int>> threeSum(vector<int>& nums) {int n = nums.size();sort(nums.begin(), nums.end());vector<vector<int>> ans;// 枚举 afor (int first = 0; first < n; ++first) {// 需要和上一次枚举的数不相同if (first > 0 && nums[first] == nums[first - 1]) {continue;}// c 对应的指针初始指向数组的最右端int third = n - 1;int target = -nums[first];// 枚举 bfor (int second = first + 1; second < n; ++second) {// 需要和上一次枚举的数不相同if (second > first + 1 && nums[second] == nums[second - 1]) {continue;}// 需要保证 b 的指针在 c 的指针的左侧while (second < third && nums[second] + nums[third] > target) {--third;}// 如果指针重合,随着 b 后续的增加// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环if (second == third) {break;}if (nums[second] + nums[third] == target) {ans.push_back({nums[first], nums[second], nums[third]});}}}return ans;}
};作者:LeetCode-Solution
链接:https://leetcode.cn/problems/3sum/solution/san-shu-zhi-he-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

题目75 颜色分类 循环嵌套小心越界

  1. 循环 后面的条件是下标的保证,内循环容易私改变量导致下标越界。
  2. 利用各种跳出,保证局部循环变量一次循环只改变一次。

代码一由于内循环改变了局部循环变量,导致下标越界

class Solution {public:void sortColors(vector<int>& nums) {int left = 0, right = nums.size() - 1;// 把2放到后面while (left < right)// make sure the index is valid{while (right >= 0 && nums[right] == 2)  { right--;   // !!!容易有bug}if (nums[left] == 2)// 这个时候 right指针 满足要求{swap(nums[left], nums[right]);}left++;}// 把1放到后面left = 0;right = nums.size() - 1;while(left < right){while (right >= 0 && nums[right] !=0){right--;// !!!容易有bug}if (nums[left]==1) swap(nums[left], nums[right]);left++;}}
};

修改后


void sortColors(vector<int>& nums) {int left = 0, right = nums.size() - 1;// 把2放到后面while (left < right)// make sure the index is valid{if (right >= 0 && nums[right] == 2){right--;continue;}if (nums[left] == 2)// 这个时候 right指针 满足要求{swap(nums[left], nums[right]);}left++;}// 把1放到后面left = 0;right = nums.size() - 1;while (left < right){if (right >= 0 && nums[right] != 0){right--;continue;}if (nums[left] == 1) swap(nums[left], nums[right]);left++;}
}

刷题 - 算法(一)相关推荐

  1. DSt:数据结构的最强学习路线之数据结构知识讲解与刷题平台、刷题集合、问题为导向的十大类刷题算法(数组和字符串、栈和队列、二叉树、堆实现、图、哈希表、排序和搜索、动态规划/回溯法/递归/贪心/分治)总

    DSt:数据结构的最强学习路线之数据结构知识讲解与刷题平台.刷题集合.问题为导向的十大类刷题算法(数组和字符串.栈和队列.二叉树.堆实现.图.哈希表.排序和搜索.动态规划/回溯法/递归/贪心/分治)总 ...

  2. 兜兜的乐扣刷题算法小记(不停更)

      根据题目,分析数据,找到规律!!!!!!!!题目数量很多,要想基本都会,就必须多练多见.量变导致质变.         "画图"帮助理解 Recursive Tree.     ...

  3. 【牛客刷题-算法】 NC19 连续子数组的最大和

    个人主页:清风莫追 推荐一款好用的面试.刷题神器牛客网:

  4. [刷题]算法竞赛入门经典 3-10/UVa1587 3-11/UVa1588

    书上具体所有题目:http://pan.baidu.com/s/1hssH0KO 题目:算法竞赛入门经典 3-10/UVa1587:Box 代码: //UVa1587 - Box #include&l ...

  5. [刷题]算法竞赛入门经典(第2版) 4-1/UVa1589 - Xiangqi

    书上具体所有题目:http://pan.baidu.com/s/1hssH0KO 代码:(Accepted,0 ms) //UVa1589 #include<iostream> #incl ...

  6. [刷题]算法竞赛入门经典(第2版) 6-7/UVa804 - Petri Net Simulation

    题意:模拟Petri网的执行.虽然没听说过Petri网,但是题目描述的很清晰. 代码:(Accepted,0.210s) //UVa804 - Petri Net Simulation //Accep ...

  7. [刷题]算法竞赛入门经典(第2版) 6-6/UVa12166 - Equilibrium Mobile

    题意:二叉树代表使得平衡天平,修改最少值使之平衡. 代码:(Accepted,0.030s) //UVa12166 - Equilibrium Mobile //Accepted 0.030s //# ...

  8. [刷题]算法竞赛入门经典(第2版) 4-3/UVa220 - Othello

    书上具体所有题目:http://pan.baidu.com/s/1hssH0KO 代码:(Accepted,0 ms) //UVa 220 - Othello #include<iostream ...

  9. [刷题]算法竞赛入门经典(第2版) 5-2/UVa1594 - Ducci Sequence

    书上具体所有题目:http://pan.baidu.com/s/1hssH0KO 代码:(Accepted,20 ms) //UVa1594 - Ducci Sequence #include< ...

最新文章

  1. 小shell脚本---查找目录下面包含string的文件
  2. 年过四十的男人,为何路越走越窄?
  3. 【今日头条】头条号图文发布页面的“扩展链接”是干嘛用的?
  4. 分析Linux内核5.0系统调用处理过程
  5. 幂等性概念及数据库乐观锁机制
  6. Python 执行代码的两种方式
  7. 判断浏览器是否为手机端 is mobile
  8. linux下网络编程设置非阻塞,UNIX网络编程 非阻塞connect的实现
  9. 日志文件设计学习(一)
  10. linux /etc/profile文件,Linux 配置文件 /etc/profile
  11. Linux下的iscsi(设备的共享服务)
  12. php无限次执行函数,php – 防止多次执行JavaScript函数
  13. 网页排序向量计算和改进
  14. oracle 各种学习资料
  15. 飞客蠕虫专杀工具_案例-飞客蠕虫攻击
  16. java反射机制面试_java反射机制面试题及答案整理,java反射面试题
  17. 日语N2听力常用词汇
  18. 2.1 随从图标的创建———自制卡牌游戏之旅
  19. word图文混排复制到UEditor图片不显示
  20. go中宕机与恢复 panic/recover 介绍

热门文章

  1. 高中人民教育出版社信息技术必修1 p63评定体重指数等级试题
  2. 函授大专计算机专业,函授大专报什么专业好?
  3. 软件开发中的3P和1A
  4. 如何运行android sdk sample中的单元测试
  5. 03.20 Linux文件属性
  6. springboot毕设茶会微电影评价系统37iza(java+VUE+Mybatis+Maven+Mysql)
  7. linux 下添加环境变量(使用 path = $path... )
  8. Go语言的SDK是什么
  9. BIOS 与 CPU关系
  10. 盘点那些具有特色的写作软件