JZ01 赋值运算符号函数

JZ02 替换空格

  1. class Solution {
  2. public:
  3. string replaceSpace(string s) {
  4. int spaceNum = count(s.begin(), s.end(), ' ');
  5. if (spaceNum==0) return s;
  6. s.append(spaceNum*2, '0');
  7. int i=s.size()-1, j=i-spaceNum*2;
  8. while (j >= 0)
  9. {
  10. if (s[j] == ' ')
  11. {
  12. j--;
  13. s[i--] = '0', s[i--] = '2', s[i--] = '%';
  14. continue;
  15. }
  16. s[i--] = s[j--];
  17. }
  18. return s;
  19. }
  20. };

JZ03. 数组中重复的数字

题目
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:[2, 3, 1, 0, 2, 5, 3]输出:2 或 3
思路

  1. 先排序,第一次出现相连元素相同则返回。
  2. 根据题目条件,从头开始,依次将元素归位((0,0),(1,1)……),当第一次出现当前元素和前一个元素相等时(这里指的是程序中的,不是真实数组里第一次出现相等),返回。

    1. /*
    2. 当前面的元素都是连续的时候,返回的是第一个重复的元素,
    3. 当不连续时,返回的不一定是一个次重复的元素。
    4. */
    5. class Solution {
    6. public:
    7. int findRepeatNumber(vector<int>& nums) {
    8. for(int i=0;i<nums.size();++i){
    9. while(nums[i]!=i){ //将元素依次归位
    10. if(nums[i] == nums[nums[i]])//当前元素和前一个元素相等
    11. return nums[i];
    12. swap(nums[i],nums[nums[i]]);
    13. }
    14. }
    15. return 0;
    16. }
    17. };

    JZ06 从尾到头打印链表

    思路

  3. 使用栈

  4. 采用递归(递归层数深了效率降低)

    1. class Solution {
    2. public:
    3. vector<int> reversePrint(ListNode* head) {
    4. vector<int> res;
    5. stack<ListNode *> s;
    6. //插入stack
    7. ListNode* pHead = head;
    8. while(pHead != nullptr){
    9. s.push(pHead);
    10. pHead = pHead->next;
    11. }
    12. //打印
    13. while(!s.empty()){
    14. res.push_back(s.top()->val);
    15. s.pop();
    16. }
    17. return res;
    18. }
    19. };

    JZ07 重建二叉树(hard)

    题目描述
    输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
    思路
    根据前序(根左右)和中序(左根右)的特点
    前序第一个为根确定区间的根节点,根据中序确定根节点的左右子树。
    依次递归区间,最后返回节点。image.png

    1. /**
    2. * Definition for binary tree
    3. * struct TreeNode {
    4. * int val;
    5. * TreeNode *left;
    6. * TreeNode *right;
    7. * TreeNode(int x) : val(x), left(NULL), right(NULL) {}
    8. * };
    9. */
    10. class Solution {
    11. public:
    12. TreeNode* helper(vector<int> &pre, int ps, int pe, vector<int> &vin, int vs, int ve){
    13. if(ps>pe) return nullptr;
    14. TreeNode *node = new TreeNode(pre[ps]);
    15. int idx = vs;
    16. //找到区间中的中序遍历的根节点的索引idx
    17. for(;idx<=ve;++idx)
    18. if(vin[idx]==pre[ps])
    19. break;
    20. int subSpaceLength = idx - vs;//左子区间的长度
    21. node->left = helper(pre, ps+1,ps+subSpaceLength, vin, vs, idx-1);
    22. node->right = helper(pre,ps+subSpaceLength+1, pe, vin, idx+1, ve);
    23. return node;
    24. }
    25. TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
    26. return helper(pre,0,pre.size()-1,vin,0,vin.size()-1);
    27. }
    28. };

    ```cpp class Solution { private: unordered_map index;

public: TreeNode* myBuildTree(const vector& preorder, const vector& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) { if (preorder_left > preorder_right) { return nullptr; }

  1. // 前序遍历中的第一个节点就是根节点
  2. int preorder_root = preorder_left;
  3. // 在中序遍历中定位根节点
  4. int inorder_root = index[preorder[preorder_root]];
  5. // 先把根节点建立出来
  6. TreeNode* root = new TreeNode(preorder[preorder_root]);
  7. // 得到左子树中的节点数目
  8. int size_left_subtree = inorder_root - inorder_left;
  9. // 递归地构造左子树,并连接到根节点
  10. // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
  11. root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
  12. // 递归地构造右子树,并连接到根节点
  13. // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
  14. root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
  15. return root;
  16. }
  17. TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
  18. int n = preorder.size();
  19. // 构造哈希映射,帮助我们快速定位根节点
  20. for (int i = 0; i < n; ++i) {
  21. index[inorder[i]] = i;
  22. }
  23. return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
  24. }

};

作者:LeetCode-Solution 链接:https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solution/cong-qian-xu-yu-zhong-xu-bian-li-xu-lie-gou-zao-9/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  1. <a name="vzz1x"></a>
  2. ## [JZ08 二叉树的下一个节点](https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&&tqId=11210&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
  3. **题目描述**<br />给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。<br />思路
  4. <font color='red' size=5></font>
  5. <a name="CpoVl"></a>
  6. ## [JZ09 变态跳台阶](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&&tqId=11162&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
  7. **题目描述**<br />一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。<br />思路<br /><font color='red' size=5></font>
  8. <a name="awmYZ"></a>
  9. ## [JZ10矩形覆盖](https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&&tqId=11163&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
  10. 题目描述<br />我们可以用2_1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2_1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?<br />思路<br />动态规划:写出状态转移方程即可
  11. ```cpp
  12. class Solution {
  13. public:
  14. int rectCover(int number) {
  15. if(0==number || 1==number || 2==number)
  16. return number;
  17. int a = 1;
  18. int b = 2,c;
  19. for (int i = 3; i <= number; ++i) {
  20. c = a +b;
  21. a = b;
  22. b = c;
  23. }
  24. return c;
  25. }
  26. };

JZ11 旋转数组的最小数字

题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
输入
[3,4,5,1,2]
返回值
1
思路
mid的两边一遍有序,一边无序
采用二分法缩小查找范围。根据所给的实例可以推断出当:
array[mid] >= array[low]时,则所选值在右边的区间。
反之,则在左边的区间。
low<high-1:当low=high-1时,low和high相连,直接返回high就是最小值。

  1. class Solution {
  2. public:
  3. int minNumberInRotateArray(vector<int> rotateArray) {
  4. if(rotateArray.size()==0) return 0;
  5. int len = rotateArray.size();
  6. int high = len-1, low = 0;
  7. while(low<high-1) //这里的high-1很关键,不知道为啥
  8. {
  9. int mid = low + (high-low)/2;
  10. if(rotateArray[mid]>=rotateArray[low])
  11. low = mid;
  12. else
  13. high = mid;
  14. }
  15. return rotateArray[high];
  16. }
  17. };

上面的解法不是常规的二分模板

待验证

  1. int minArray(vector<int>& numbers)
  2. {
  3. int l = 0, r = numbers.size()-1;
  4. while (l < r)
  5. {
  6. int mid = l + r >> 1;
  7. if (numbers[mid] <= numbers[r]) r = mid;
  8. else l = mid + 1;
  9. }
  10. return numbers[l];
  11. }

此种解法无法处理「3,3,1,3」这种情况

当出现mid和r相等时,r前移

  1. class Solution {
  2. public:
  3. int minArray(vector<int>& numbers)
  4. {
  5. int l = 0, r = numbers.size()-1;
  6. while (l < r)
  7. {
  8. int mid = l + r >> 1;
  9. if (numbers[mid] < numbers[r]) r = mid;
  10. else if (numbers[mid] > numbers[r]) l = mid + 1;
  11. else r--;
  12. }
  13. return numbers[l];
  14. }
  15. };

JZ12 矩阵中的路径(hard)

题目
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[[“a”,”b“,”c”,”e”],[“s”,”f“,”c“,”s”],[“a”,”d”,”e“,”e”]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
示例 1:
输入:board = [[“A”,”B”,”C”,”E”],[“S”,”F”,”C”,”S”],[“A”,”D”,”E”,”E”]], word = “ABCCED”
输出:true
思路
剑指offer - 图2
link

  1. class Solution {
  2. public:
  3. int x[4] = {0,0,-1,1}; //x[4]、y[4]和dfs中for循环,共同构成遍历字符board[i][j]的“左右上下”四个字符这一功能
  4. int y[4] = {-1,1,0,0}; // 这里用的特别巧妙
  5. int rows,cols;
  6. bool dfs(vector<vector<char>>& board,string word,int i,int j,int num) {
  7. if (num == word.size()) return true;
  8. char tmp = board[i][j]; // 用于暂时保存字符
  9. board[i][j] = '.'; // 代表这个字符已经访问过
  10. for (int k = 0; k < 4; k++) {
  11. int d_x = x[k] + i; // 新的i值
  12. int d_y = y[k] + j; // 新的j值
  13. if (d_x >= 0 && d_x < rows && d_y >= 0 && d_y < cols && word[num] == board[d_x][d_y]) {
  14. if (dfs(board,word,d_x,d_y,num + 1)) return true;
  15. }
  16. }
  17. board[i][j] = tmp; // 遍历结束后改回来
  18. return false;
  19. }
  20. bool exist(vector<vector<char>>& board, string word) {
  21. rows = board.size();
  22. cols = board[0].size();
  23. for (int i = 0; i < rows; i++) {
  24. for (int j = 0; j < cols; j++) {
  25. if (board[i][j] == word[0]) { //若是没有这一步的判断,则会报错如两个a a
  26. if (dfs(board,word,i,j,1))
  27. return true;
  28. }
  29. }
  30. }
  31. return false;
  32. }
  33. };

link

  1. class Solution {
  2. public:
  3. bool hasPath(char* matrix, int rows, int cols, char* str)
  4. {
  5. // 只通过76%
  6. //if(matrix==nullptr || cols<1 || cols<1 || str==nullptr)
  7. // return false;
  8. if(rows == 1 && cols == 1){
  9. if(matrix[0] == str[0])
  10. return true;
  11. else
  12. return false;
  13. }
  14. for(int i=0; i < rows; i++){
  15. for(int j=0; j < cols; j++){
  16. if(dfs(matrix, rows, cols, i, j, str, 0)){
  17. return true;
  18. }
  19. }
  20. }
  21. return false;
  22. }
  23. bool dfs(char* matrix, int rows, int cols, int x, int y, char* str, int s){
  24. if(str[s] == '\0') return true;
  25. int dx[] = {0, 0, -1, 1};
  26. int dy[] = {1, -1, 0, 0};
  27. for(int i=0; i<4; i++){
  28. int row = x+dx[i];
  29. int col = y+dy[i];
  30. if(row>=0 && row<rows && col>=0 && col<cols && matrix[row*cols+col]==str[s]){
  31. char t = str[s];
  32. matrix[row*cols+col] = '*';
  33. if(dfs(matrix, rows, cols, row, col, str, s+1))
  34. return true;
  35. matrix[row*cols+col] = t;
  36. }
  37. }
  38. return false;
  39. }
  40. };

JZ13 机器人的运动范围

题目描述
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
思路
常规思路

  1. class Solution {
  2. public:
  3. int movingCount(int threshold, int rows, int cols)
  4. {
  5. if (threshold < 0 || rows < 1 || cols < 1)
  6. return 0;
  7. bool* visited = new bool[rows * cols];
  8. memset(visited, false, rows * cols);
  9. int count = movingCountCore(threshold, rows, cols, 0, 0, visited);
  10. delete[] visited;
  11. return count;
  12. }
  13. int movingCountCore(int threshold, int rows, int cols, int row, int col, bool* visited)
  14. {
  15. int count = 0;
  16. if (check(threshold, rows, cols, row, col, visited))//可以访问(row, col)
  17. {
  18. visited[row * cols + col] = true;
  19. count = 1 + movingCountCore(threshold, rows, cols, row - 1, col, visited)
  20. + movingCountCore(threshold, rows, cols, row + 1, col, visited)
  21. + movingCountCore(threshold, rows, cols, row, col - 1, visited)
  22. + movingCountCore(threshold, rows, cols, row, col + 1, visited);
  23. }
  24. return count;
  25. }
  26. bool check(int threshold, int rows, int cols, int row, int col, bool* visited)
  27. {
  28. if (row >= 0 && row < rows && col >= 0 && col < cols /*未越界*/
  29. && threshold >= getDigitSum(row) + getDigitSum(col) /*满足题目条件*/
  30. && !visited[row * cols + col]) /*未访问*/
  31. return true;
  32. return false;
  33. }
  34. int getDigitSum(int number)
  35. {
  36. int sum = 0;
  37. while (number > 0)
  38. {
  39. sum += number % 10;
  40. number /= 10;
  41. }
  42. return sum;
  43. }
  44. };

下面的解法更加通俗一些

这里我前面思路有些许问题,总觉得要判断周围的点是否访问过,才能dfs当前点, 没有绕过来,如果没有访问到周围的点,当前点也递归不到啊, hhhhha

  1. class Solution {
  2. private:
  3. vector<vector<bool>> vis;
  4. int m, n;
  5. int ans;
  6. vector<pair<int, int>> directions = {{-1,0}, {1,0}, {0,1},{0,-1}};
  7. public:
  8. int movingCount(int m, int n, int k) {
  9. this->m = m, this->n = n;
  10. ans = 0;
  11. vis = vector<vector<bool>>(m, vector<bool>(n,false));
  12. dfs(0, 0, k);
  13. return ans;
  14. }
  15. void dfs(int x, int y, int k)
  16. {
  17. if (x<0 || x>=m || y<0 || y>=n || vis[x][y] || !isMovable(x,y,k))
  18. return;
  19. ans++;
  20. vis[x][y] = true;
  21. for (auto &dir : directions)
  22. {
  23. int dx = x + dir.first, dy = y + dir.second;
  24. dfs(dx, dy, k);
  25. }
  26. }
  27. bool isMovable(int x, int y, int k)
  28. {
  29. vector<int> arr{x, y};
  30. int res = 0;
  31. for (auto &i : arr)
  32. {
  33. while (i)
  34. {
  35. res += i%10;
  36. i /= 10;
  37. }
  38. }
  39. return res <= k;
  40. }
  41. };

第3章 高质量的代码

JZ16数值的整数次方

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
思路
注意exponent为负数的情况

  1. double Power(double base, int exponent) {
  2. double result=1;
  3. bool negative = false;
  4. if(exponent < 0){
  5. negative = true;
  6. exponent = -exponent;
  7. }
  8. for(int i=1;i<=exponent;++i){
  9. result *= base;
  10. }
  11. if(negative)
  12. result = 1/result;
  13. return result;
  14. }

log(N)解法,二进制优化

  1. double myPow(double x, int n)
  2. {
  3. double res = 1, cur = x;
  4. bool neg = false;
  5. if (n<0) neg = true, n = -n;
  6. while (n)
  7. {
  8. if (n&0x01) res *= cur;
  9. cur *= cur;
  10. n >>= 1;
  11. }
  12. if (neg) res = 1/res;
  13. return res;
  14. }

JZ17. 打印从1到最大的n位数

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
示例 1:

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

  1. void printNumber(char* number)
  2. {
  3. int len = strlen(number);
  4. bool isBeginning0 = true;
  5. for (int i = 0; i < len; ++i)
  6. {
  7. if (isBeginning0 && number[i] != '0')
  8. isBeginning0 = false;
  9. if (!isBeginning0)
  10. printf("%c", number[i]);
  11. }
  12. printf("\t");
  13. }
  14. void dfs(char* number, int length, int idx)
  15. {
  16. if (idx == length - 1)
  17. {
  18. printNumber(number);
  19. return;
  20. }
  21. for (int i = 0; i < 10; ++i)
  22. {
  23. number[idx+1] = '0' + i;
  24. dfs(number, length, idx + 1);
  25. }
  26. }
  27. void priprint1ToMaxOfNDigitsnt(int n)
  28. {
  29. if (n <= 0)
  30. return;
  31. char* number = new char[n + 1];
  32. number[n] = '\0';
  33. for (int i = 0; i < 10; ++i)
  34. {
  35. number[0] = i + '0';
  36. dfs(number, n, 0);
  37. }
  38. delete[] number;
  39. }
## JZ23 链表中环的入口结点 题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路
image.png
假设快指针和满指针在Z点相遇。
由于a的长度未知,故假设:相遇时,fast已绕环n圈。则:
slow前进的距离是:a+b
fast 前进的距离是:a+n(c+b)+b
又有 2
slow = fast ===> a = (n-1)(b+c)+c
a 的长度是(n-1)圈,加c的长度,
则一个点从z点出发(多余的c长度),一个点从起点出发。
肯定在Y点相遇。 cpp /** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode *detectCycle(ListNode *head) { if(head == nullptr) return NULL;//头结点为空 ListNode* fast = head; ListNode* slow = head; while(fast!=nullptr && fast->next!=nullptr) { fast = fast->next->next; slow = slow->next; if(fast==slow)//相遇 break; } if(fast==nullptr || fast->next==nullptr) //没有环 return NULL; else { slow = head;//重置slow从起点开始 while(fast != slow) { fast = fast->next; slow = slow->next; } return slow; } } }; /*链表中的++没有重载时,和next不一样的。*/ ## JZ18-2删除链表中的的重复节点(hard) 参考书上的答案 cpp void deleteDuplication(ListNode* pHead) { if (pHead == NULL) return; ListNode* pPreNode = NULL; ListNode* pNode = pHead; while (pNode != NULL) { ListNode* pNext = pNode->next; bool needDelete = false; if (pNext != NULL && pNext->val == pNode->val)/*出现重复节点*/ needDelete = true; if (!needDelete) {/*do not need delete*/ pPreNode = pNode; pNode = pNode->next; } else { /*需要删除时*/ int val = pNode->val; ListNode* pToBeDel = pNode; /**/ while (pNext != NULL && pToBeDel->val == val) { pNext = pToBeDel->next; /*delete pToBeDel; pToBeDel = NULL;*/ pToBeDel = pNext; } /*循环结束时,pNext指向了第一个不相同的节点,比如图中的节点5*/ if (pPreNode == NULL)/*最开始的节点出现重复时*/ pHead = pNext; else pPreNode->next = pNext; pNode = pNext; } } } 下面的解法是我自己写的,显然是没有考虑到一些特殊情况的,如从root开始就出现重复的情况 cpp ListNode* deleteDuplication(ListNode* pHead) { if (pHead == NULL) return NULL; bool duplicate = false; ListNode* pre = NULL, * cur = pHead, * newnode = pHead; while (cur->next != NULL) { auto node = cur->next; if (node->val != newnode->val) { if (duplicate) { pre->next = node; newnode = node; } else { pre = newnode; newnode = node; duplicate = false; } } else { duplicate = true; } cur = cur->next; } return pHead; } ## JZ21调整数组顺序使奇数位于偶数前面 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。 cpp void reOrderArray(vector<int> &array) { int n = array.size(); if(0==n || 1==n) return; vector<int> even; int oddsNumber = 0; for(int i=0;i<n;++i){ if(array[i]&0x01) array[oddsNumber++] = array[i]; else even.emplace_back(array[i]); } int k =0; for(int i=oddsNumber;i<n;++i) array[i] = even[k++]; } 这里直接使用partition中交换的思路,不用开辟空间 cpp vector<int> exchange(vector<int>& nums) { int l = 0, r = nums.size()-1; while (l<r) { while (l<nums.size() && nums[l]&0x01) l++; while (r>=0 && nums[r]%2==0) r--; if (l < r) swap(nums[l], nums[r]); } return nums; } ## JZ22链表的倒数第K个节点 常见的解法是 cpp ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) { if (pListHead == NULL || k == 0) return NULL; ListNode* fast = pListHead, * slow = pListHead; for (unsigned int i = 0; i < k; --i) { fast = fast->next; } while (fast != NULL) { fast = fast->next; slow = slow->next; } return slow; } 这个解法是有问题的,当k大于链表的长度就会出现溢出问题。 cpp ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) { if(pListHead==nullptr || k==0) return nullptr; ListNode *fast = pListHead ,* slow=pListHead; while(k--){ if(fast) fast = fast->next; else return nullptr; } while(fast!=nullptr){ fast = fast->next; slow = slow->next; } return slow; } ## JZ23 链表中环的入口结点 题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路
image.png
假设快指针和满指针在Z点相遇。
由于a的长度未知,故假设:相遇时,fast已绕环n圈。则:
slow前进的距离是:a+b
fast 前进的距离是:a+n(c+b)+b
又有 2
slow = fast ===> a = (n-1)(b+c)+c
a 的长度是(n-1)圈,加c的长度,
则一个点从z点出发(多余的c长度),一个点从起点出发。
肯定在Y点相遇。 cpp /** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode *detectCycle(ListNode *head) { if(head == nullptr) return NULL;//头结点为空 ListNode* fast = head; ListNode* slow = head; while(fast!=nullptr && fast->next!=nullptr) { fast = fast->next->next; slow = slow->next; if(fast==slow)//相遇 break; } if(fast==nullptr || fast->next==nullptr) //没有环 return NULL; else { slow = head;//重置slow从起点开始 while(fast != slow) { fast = fast->next; slow = slow->next; } return slow; } } }; /*链表中的++没有重载时,和next不一样的。*/ ## JZ24 反转链表 解题思路
在反转的过程中,反转节点的下一个节点一定要提前保存,否则,后面的节点都丢失了。 cpp ListNode* reverseList(ListNode* head) { if (head == nullptr) return nullptr; ListNode* cur=nullptr; ListNode* pre=head; while (pre != nullptr) { ListNode* node = pre->next; /*先保存,断开之后就找不到了*/ pre->next = cur; cur = pre; pre = node; } return cur; } ## JZ25 合并两个有序链表 image.png
写的时候一定要有清晰的思路 cpp class Solution { public: ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { if(l1 == NULL) return l2; else if(l2 == NULL) return l1; else if(l1->val<l2->val){ l1->next = mergeTwoLists(l1->next, l2); return l1; } else{ l2->next = mergeTwoLists(l1, l2->next); return l2; } } }; ## JZ26 树的子结构 # 第4章 解决面试题的思路 ## JZ27 二叉树的镜像 思路
所有的树都是小的子树构成的。依次交换
将节点插入队尾并弹出对头,进行交换。 cpp void Mirror(TreeNode* pRoot) { if (pRoot == nullptr) return; queue<TreeNode*> q; q.push(pRoot); while (!q.empty()) { auto node = q.front(); q.pop(); if (node->left != NULL) q.push(node->left); if (node->right != NULL) q.push(node->right); if (node->left != NULL || node->right != NULL)/*swap left and right*/ { auto tmp = node->right; node->right = node->left; node->left = tmp; } } } ## JZ28 对称的二叉树 思路
尝试使用的层序遍历的思路。但是有以下的情况是无法突破的。 cpp class Solution01 { public: bool isSymmetrical(TreeNode* pRoot) { return bfs_isBlanced(pRoot); } bool bfs_isBlanced(TreeNode* root) { if (root == nullptr) return true; TreeNode* node = root; queue<TreeNode*> q; q.push(node); int len = 0; while (!q.empty()) { int curLevelSize = q.size(); ++len; vector<int> res; for (int i = 0; i < curLevelSize; ++i) { node = q.front(); q.pop(); res.emplace_back(node->val); if (node->left) q.push(node->left); if (node->right) q.push(node->right); } int newSize = q.size(); if (len > 1 && newSize & 0x01) { return false; } else { int l = 0, r = curLevelSize - 1; while (l <= r) { if (res[l++] != res[r--]) return false; } } res.clear(); } return true; } }; 转换思路,采用书上的方法。递归法
剑指offer - 图6
若满足对称二叉树,必须满足:
1. L->val == R->val2. L->left->val == R->right->val3. L->right->val == R->left->val cpp class Solution { public: bool isSymmetrical(TreeNode* pRoot) { return isSymmetrical(pRoot, pRoot); } bool isSymmetrical(TreeNode* node1, TreeNode* node2) { if(node1==NULL && node2==NULL) return true; if(node1==NULL || node2==NULL) return false; if(node1->val != node2->val) return false; return isSymmetrical(node1->left, node2->right) && isSymmetrical(node1->right, node2->left); } }; ## JZ29 顺时针打印矩阵 解题思路
注意特殊情况的处理:
right —> left && down —> up 时需要先判断。 cpp class Solution { public: vector<int> spiralOrder(vector<vector<int> >& matrix) { int rows = matrix.size(); int cols = matrix[0].size(); vector<int> res; if (rows == 0 || cols == 0) return res; int left = 0, right = cols-1; int up = 0, down = rows-1; while (left <= right && up <= down) { for (int i = left; i <= right; ++i)//left --> right res.push_back(matrix[up][i]); up++; for (int i = up; i <= down; ++i)//up --> down res.push_back(matrix[i][right]); --right; /*这里要注意特殊情况,所剩区域是一个(right-left+1)*1的矩形*/ if(down>=up) //right --> left for (int i = right; i >= left; --i) res.push_back(matrix[down][i]); --down; if(right>=left)// down --> up for (int i = down; i >= up; --i) res.push_back(matrix[i][left]); ++left; } return res; } }; void main_test() { vector<vector<int>> matrix = { {1,2,3},{4,5,6},{7,8,9} }; Solution so; for (auto i : so.spiralOrder(matrix)) printf("%d \t", i); } 个人觉得下面的解法更为优雅 cpp class Solution { private: vector<pair<int, int>> direction = {{0,1}, {1,0}, {0,-1},{-1,0}}; public: vector<int> spiralOrder(vector<vector<int>>& matrix) { if (matrix.size() == 0 || matrix[0].size() == 0) { return {}; } int m = matrix.size(), n = matrix[0].size(); vector<vector<bool>> vis(m, vector<bool>(n, false)); vector<int> res; int i = 0, j = 0, idx = 0, cnt = 0; for ( ; cnt < m*n; cnt++) { res.emplace_back(matrix[i][j]); vis[i][j] = true; int nextI = i + direction[idx].first, nextJ = j + direction[idx].second; if (nextI<0 || nextI>=m || nextJ<0 || nextJ>=n || vis[nextI][nextJ]) idx = (idx + 1) % 4; i += direction[idx].first, j += direction[idx].second; } return res; } }; ## JZ30 包含min函数的栈 ## JZ31 栈的压入和弹出序列 自己独立写的哈 cpp bool validateStackSequences(vector<int>& pushed, vector<int>& popped) { stack<int> sPush, sPop; for (int i=0, j=0; i<pushed.size(); ) { sPush.push(pushed[i++]); while (!sPush.empty() && sPush.top() == popped[j]) { sPush.pop(); j++; } } return sPush.empty(); } ## JZ32-1 从上往下打印二叉树 cpp class Solution { public: vector<int> PrintFromTopToBottom(TreeNode* root) { vector<int> res; if (root == nullptr) return res; TreeNode* node = root; queue<TreeNode*> q; q.push(node); while (!q.empty()) { int curLevelSize = q.size(); for (int i = 0; i < curLevelSize; ++i) { node = q.front(); q.pop(); res.emplace_back(node->val); if (node->left) q.push(node->left); if (node->right) q.push(node->right); } } return res; } }; ## JZ32-3 之字形打印二叉树 思路
层序遍历 cpp class Solution { public: vector<vector<int> > Print(TreeNode* pRoot) { vector<vector<int>> res; if (pRoot == NULL) return res; bfs(pRoot, res); return res; } void bfs(TreeNode* root, vector<vector<int>>& res) { TreeNode* node = root; queue<TreeNode*> q; q.push(node); vector<int> ans; bool flag = true; while (!q.empty()) { int curLevelSize = q.size(); for (int i = 0; i < curLevelSize; ++i) { node = q.front(); q.pop(); ans.emplace_back(node->val); if (node->left) q.push(node->left); if (node->right) q.push(node->right); } flag = !flag; if (flag) reverse(ans.begin(), ans.end()); res.emplace_back(ans); ans.clear(); } } }; ## JZ33 二叉搜索树的后序遍历序列 ## JZ34 二叉树中和为某一值的路径 采用DFS进行深度优先遍历,然后采用回溯法,得到想要的结果。
注意一些代码中的细节问题。 cpp /* struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) { } };*/ class Solution { public: vector<vector<int> > FindPath(TreeNode* root,int expectNumber) { vector<vector<int> > res; if (root == nullptr) return res; vector<int> path; helper(root, res, expectNumber, path, 0); return res; } void helper(TreeNode* node, vector < vector<int> >& res, int expectsum, vector<int>&path,int curSum) { curSum = curSum + node->val; path.emplace_back(node->val); bool isLeaf = (node->left == NULL && node->right == NULL); if (curSum == expectsum && isLeaf) { res.push_back(path); path.pop_back(); /*形成路径后要返回父节点,由于直接返回 跳过了弹出节点,需要进行回溯*/ return; } if (node->left != NULL) helper(node->left, res, expectsum, path, curSum); if(node->right!=NULL) helper(node->right, res, expectsum, path, curSum); path.pop_back(); /*返回父节点就回溯*/ } }; hha
发现我现在的代码更加的简洁了,xixi cpp class Solution { private: vector<vector<int>> res; vector<int> cur; public: vector<vector<int>> pathSum(TreeNode* root, int target) { if (root == nullptr) return res; dfs(root, target); return res; } void dfs(TreeNode* node, int target) { cur.push_back(node->val); if (target==node->val && node->left==nullptr && node->right==nullptr) { res.push_back(cur); cur.pop_back(); // it's likely to forget backtrace return ; } if (node->left) dfs(node->left, target - node->val); if (node->right) dfs(node->right, target - node->val); cur.pop_back(); // backtrace } }; ## JZ35 复杂链表的复制 ## JZ36 二叉搜索树与双向链表 ## JZ37 二叉树的序列化与反序列化 ## JZ38 字符串的排列 # 第5章 优化时间和空间效率 ## JZ39 数组中出现次数超过一半的数 哈希表统计法:
遍历数组 nums ,用 HashMap 统计各数字的数量,即可找出 众数 。此方法时间和空间复杂度均为 O(N) 数组排序法:
将数组 nums 排序,数组中点的元素 一定为众数。 以上两种方法都比较简单,就不写了 摩尔投票法:
核心理念为 票数正负抵消 。此方法时间和空间复杂度分别为 O(N)O(N) 和 O(1)O(1) ,为本题的最佳解法。
image.png shell int majorityElement(vector<int>& nums) { int x=0, vote=0; for (auto &i : nums) { if (vote == 0) x = i; vote += (x == i) ? 1 : -1; } return x; } ## 剑指 Offer 48. 最长不含重复字符的子字符串 1. 双指针解法 cpp class Solution { public: unordered_map<char, int> mp; int lengthOfLongestSubstring(string s) { int i=0, j=0, maxLen = 0; while (j < s.size()) { mp[s[j]]++; if (mp[s[j]] > 1) // dup happens, move i { while (s[i] != s[j] && i < j) { mp[s[i]]--; i++; } if (s[i] == s[j]) { mp[s[i]]--; i++; } } maxLen = max(maxLen, j-i+1); j++; } return maxLen; } }; ## 剑指 Offer 49. 丑数 cpp class Solution { public: int nthUglyNumber(int n) { vector<long> factors{2,3,5}; priority_queue<long, vector<long>, greater<long>> pq; unordered_set<long> used; long cur; pq.push(1), used.insert(1); for (int i=0; i<n; i++) { cur = pq.top(); pq.pop(); for (auto &f : factors) { long num = f * cur; if (!used.count(num)) { pq.push(num); used.insert(num); } } } return cur; } };

剑指 Offer 66. 构建乘积数组

  1. class Solution {
  2. public:
  3. vector<int> constructArr(vector<int>& a) {
  4. int n = a.size(); if (!n) return {};
  5. vector<int> forward(n, a[0]), reverse(n, a[n-1]);
  6. for (int i=1; i<n; i++) forward[i] = forward[i-1] * a[i];
  7. for (int i=n-2; i>=0; i--) reverse[i] = reverse[i+1] * a[i];
  8. vector<int> res(n, 0);
  9. res[0] = reverse[1];
  10. for (int i=1; i<n-1; i++)
  11. res[i] = forward[i-1] * reverse[i+1];
  12. res[n-1] = forward[n-2];
  13. return res;
  14. }
  15. };

这里可以将后面的两次循环合并为一次