59. 螺旋矩阵 II

image.png
这道题思路不难,但是容易写错
思路:由外向内逐圈给res赋值,用(x1,y1) 和 (x1, y2)分别定位每一圈的左上角和右下角,然后循环输出即可

  1. class Solution {
  2. public:
  3. vector<vector<int>> generateMatrix(int n) {
  4. vector<vector<int>> res(n, vector<int>(n));
  5. int x1 = 0, y1 = 0, x2 = n - 1, y2 = n - 1; //记录左上角和右下角的坐标
  6. int num = 1;
  7. while (x1 <= x2 && y1 <= y2) {
  8. int posX = x1, posY = y1;
  9. //上面一行
  10. while (posY <= y2) res[posX][posY++] = num++;
  11. //右边一列
  12. posY = y2;
  13. posX++;
  14. while (posX <= x2) res[posX++][posY] = num++;
  15. //下边一列
  16. posX = x2;
  17. posY--;
  18. while (posY >= y1) res[posX][posY--] = num++;
  19. //左边一列
  20. posY = y1;
  21. posX--;
  22. while (posX > x1) res[posX--][posY] = num++;
  23. x1++;
  24. y1++;
  25. x2--;
  26. y2--;
  27. }
  28. return res;
  29. }
  30. };

矩阵通用模板
注意++i 和 —i 别写错了

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0));
        int x1 = 0, y1 = 0; // 左上角
        int x2 = n - 1, y2 = n - 1; // 右下角
        int num = 1;
        while (x1 <= x2 && y1 <= y2) {
            for (int i = y1; i <= y2; ++i) {
                res[x1][i] = num++;
            }
            for (int i = x1 + 1; i <= x2; ++i) {
                res[i][y2] = num++;
            }
            if (x1 < x2 && y1 < y2) {
                for (int i = y2 - 1; i >= y1; --i) {
                    res[x2][i] = num++;
                }
                for (int i = x2 - 1; i >= x1 + 1; --i) {
                    res[i][x1] = num++;
                }
            }
            ++x1;
            ++y1;
            --x2;
            --y2;
        }
        return res;
    }
};

54. 螺旋矩阵

image.png

image.png

这道题和59不一样的地方在于要输出的矩阵不是方阵,因此要考虑的边界问题会更多

思路一

分层遍历,一圈一圈的处理矩阵,最后再处理不成环的情况
模拟行为 - 图5

  • 如果一条边从头遍历到底,则下一条边遍历的起点随之变化
  • 选择不遍历到底,可以减小横向、竖向遍历之间的影响
  • 一轮迭代结束时,4条边的两端同时收窄 1
  • 一轮迭代所做的事情变得很清晰:遍历一个“圈”,遍历的范围收缩为内圈
  • 一层层向里处理,按顺时针依次遍历:上、右、下、左。
  • 不再形成“环”了,就会剩下一行或一列,然后单独判断

四个边界
上边界 top : 0
下边界 bottom : matrix.length - 1
左边界 left : 0
右边界 right : matrix[0].length - 1
矩阵不一定是方阵
top < bottom && left < right 是循环的条件
无法构成“环”了,就退出循环,退出时可能是这 3 种情况之一:
top == bottom && left < right —— 剩一行
top < bottom && left == right —— 剩一列
top == bottom && left == right —— 剩一项(也是一行/列)
处理剩下的单行或单列
因为是按顺时针推入结果数组的,所以
剩下的一行,从左至右 依次推入结果数组
剩下的一列,从上至下 依次推入结果数组
代码
每个元素访问一次,时间复杂度 O(mn),m、n 分别是矩阵的行数和列数
空间复杂度 O(m
n)
Runtime: 0 ms, faster than 100.00% of Go online submissions for Spiral Matrix.

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        vector<int> res;
        int x1 = 0, y1 = 0, x2 = m - 1, y2 = n - 1; //定位左上角和右下角
        while(x1 < x2 && y1 < y2) {
            //上
            for (int i = y1; i < y2; ++i) res.push_back(matrix[x1][i]);
            //右
            for (int i = x1; i < x2; ++i) res.push_back(matrix[i][y2]);
            //下
            for (int i = y2; i > y1; --i) res.push_back(matrix[x2][i]);
            //左
            for (int i = x2; i > x1; --i) res.push_back(matrix[i][y1]);

            //收缩       
            x1++;
            y1++;
            x2--;
            y2--;
        }
        if (x1 == x2)//剩下一行
            for(int i = y1; i <= y2; ++i) res.push_back(matrix[x1][i]);
        else if (y1 == y2)//剩下一列
            for(int i = x1; i <= x2; ++i) res.push_back(matrix[i][y1]);

        return res;
    }
};

思路二

遍历到底
模拟行为 - 图6

  • 循环的条件改为: top <= bottom && left <= right
  • 每遍历一条边,下一条边遍历的起点被“挤占”,要更新相应的边界
  • 需注意到,可能在循环途中,突然不再满足循环的条件,即top > bottom或left > right,其中一对边界彼此交错了
  • 这意味着所有项都遍历完了,要break了,如果没有及时 break ,就会重复遍历

解决办法

  • 每遍历完一条边,更新相应的边界后,都加上一句if (top > bottom || left > right) break;,避免没有及时退出循环,导致重复遍历。
  • 而且,遍历完成这个时间点,要么发生在遍历完“上边”,要么发生在遍历完“右边”
  • 所以只需在这两步操作之后,加 if (top > bottom || left > right) break 即可

    class Solution {
    public:
      vector<int> spiralOrder(vector<vector<int>>& matrix) {
          int m = matrix.size(), n = matrix[0].size();
          vector<int> res;
          int x1 = 0, y1 = 0, x2 = m - 1, y2 = n - 1; //定位左上角和右下角
          while(x1 <= x2 && y1 <= y2) {
              //上
              for (int i = y1; i <= y2; ++i) res.push_back(matrix[x1][i]);
              x1++;
              //右
              for (int i = x1; i <= x2; ++i) res.push_back(matrix[i][y2]);
              y2--;
    
              //避免重复遍历
              if (x1 > x2 || y1 > y2) break;
    
              //下
              for (int i = y2; i >= y1; --i) res.push_back(matrix[x2][i]);
              x2--;
              //左
              for (int i = x2; i >= x1; --i) res.push_back(matrix[i][y1]);
              y1++;
              //收缩              
          }
          return res;
      }
    };
    
class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int> res;
        int m = matrix.size(), n = matrix[0].size();
        int x1 = 0, y1 = 0; // 左上角
        int x2 = m - 1, y2 = n - 1; // 右下角
        while (x1 <= x2 && y1 <= y2) {
            for (int i = y1; i <= y2; ++i) {
                res.push_back(matrix[x1][i]);
            }
            for (int i = x1 + 1; i <= x2; ++i) {
                res.push_back(matrix[i][y2]);
            }
            if (x1 < x2 && y1 < y2) { // 防止重复打印
                for (int i = y2 - 1; i >= y1; --i) {
                    res.push_back(matrix[x2][i]);
                }
                for (int i = x2 - 1; i >= x1 + 1; --i) {
                    res.push_back(matrix[i][x1]);
                }
            }
            ++x1;
            ++y1;
            --x2;
            --y2;
        }
        return res;
    }
};

88. 合并两个有序数组

image.png
image.png
LeetCode 88 题

  • 解法1:直接将数组2插入到数组1后面,然后对合并后的数组进行排序
class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        for (int i = 0; i < nums2.size(); ++i) {
            nums1[m + i] = nums2[i];
        }
        sort(nums1.begin(), nums1.end());
    }
};
  • 解法二:双指针

因为初始数组是有序的,因此用两个指针来遍历两个数组,每次取最小的

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int p1 = 0, p2 = 0;
        int sorted[m + n]; //用于保留合并后的数组 空间复杂度 O(m+n)
        int cur;
        while (p1 < m || p2 < n) {
            if (p1 == m) {
                cur = nums2[p2++];
            } else if (p2 == n) {
                cur = nums1[p1++];
            } else if (nums1[p1] < nums2[p2]) {
                cur = nums1[p1++];
            } else {
                cur = nums2[p2++];
            }
            sorted[p1 + p2 - 1] = cur;
        }
        for (int i = 0; i != m + n; ++i) {
            nums1[i] = sorted[i];
        }
    }
};
  • 解法3:逆向双指针

对于解法2,从头往后遍历数组的话,如果不用额外的数组,有些元素可能被覆盖,因为元素初始有序,且数组空间够大,可以使用逆向双指针,从后往前比较两个数组中较大者,放到nums1的后面,这样可以将nums1剩余的空间利用起来,不需要额外空间,空间复杂度为O(1)

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int p1 = m - 1, p2 = n - 1;
        int index = m + n - 1;
        while (p1 >= 0 && p2 >= 0) {
            if (nums1[p1] >= nums2[p2]) {
                nums1[index--] = nums1[p1--]; 
            } else {
                nums1[index--] = nums2[p2--];
            }
        }
        // 如果nums1剩余元素未处理完,则不用处理
        // 处理nums2
        while (p2 >= 0) {
            nums1[index--] = nums2[p2--];
        }
    }
};

645. 错误的集合

image.png

数组[1, 2, 2, 4]
索引 0, 1, 2, 3
i=0和i=1时,发现 nums[i] = i+1,也就是位置正确 while里的条件是false
i=2时,发现nums[i] != i+1,也就是说位置不对,
那么这个元素本来应该放的位置处的索引是index = 数值(nums[i]) - 1,因为正确的位置数值都会比索引大1
但是找到应在的位置,发现nums[index]处的值和nums[i]一样,也就是说这个元素有可能是被nums[nums[i] - 1]替换的,所以位置依然是“正确的”,所以while中的条件 nums[nums[i] - 1] != nums[i] 仍然是false
数组[2, 2, 4, 1]
索引 0, 1, 2, 3
nums[0] 不等于1,但是 nums[2-1]=nums[1]=nums[0]=2,所以暂时认为1被2替换,然后nums[1]等于2,跳过
//while
nums[2] 不等于 3并且nums[4-1]不等于4, 交换 nums[2]和nums[3],数组变为[2, 2, 1, 4],交换完发现nums[2]还是不等于3,并且nums[1-1]不等于1,接着交换,数组变为[1,2,2,4],
然后所有元素都在正确的位置了

class Solution {
public:
    vector<int> findErrorNums(vector<int>& nums) {
        for (int i = 0; i < nums.size(); i++)
        {
            while (nums[i] != i + 1 && nums[nums[i] - 1] != nums[i])
            {
                /* nums[i] != i + 1代表元素位置不对
                nums[nums[i] - 1] != nums[i] 不是被替换的元素,
                如果位置不对并且被其他元素替换了,就不用管它
                每次 while 索引i处的值都会在正确的位置

                如果输入是 [1,2,2,4]这种,这个循环没什么吊用
                如果输入是[2,2,4,1]这种位置不对的,这个循环会把数组变为[1,2,2,4]
                */
                swap_index(nums, i, nums[i] - 1);
            }
        }

        for (int i = 0; i < nums.size(); i++)
        {
            //找到被覆盖的元素,那么重复的元素就是nums[i],被覆盖的就是 i+1
            if (nums[i] != i + 1)
                return vector<int>{nums[i], i + 1};
        }

        return vector<int>{};
    }
    void swap_index(vector<int> &nums, int i, int j)
    {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
};

2022.03.21版本
集合大小为n,元素为 1 ~ n,假设有一个数组存放这n个元素,将数组排序后,假设数组下标为1,应有
nums[i] = i + 1,成这个元素位于“正确”的位置
现在,题目给的数组是无序的,可以想办法将元素置于“正确”的位置:
循环遍历数组,对于每一个nums[i],都可以通过循环使nums[i] = i + 1,假如nums[i] != i + 1,设nums[i]= t , 那么尝试将其与nums[t - 1]处的元素交换,交换后,nums[t - 1] = t,一个元素找到“正确”的位置,每次循环都会有一个元素找到正确位置,因此,所有元素都处理完毕,时间复杂度为O(N),假如nums[t - 1] = t,说明 t重复,不必交换,这时候只知道重复的是哪个,缺失的是谁还不能判断,因此不能直接输出

for (int i = 0; i < nums.size(); ++i) {
    while (nums[i] != i + 1 && nums[nums[i] - 1] != nums[i]) {
        int temp = nums[i];
        nums[i] = nums[nums[i] - 1];
        nums[temp - 1] = temp; 
    }
}

数组处理完毕后,再遍历一次数组,如果nums[i] != i + 1,则重复元素为nums[i],缺失元素为 i + 1

for (int i = 0; i < nums.size(); ++i) {
    if (nums[i] != i + 1) {
        return {nums[i], i + 1};
    }
}

完整代码:

class Solution {
public:
    vector<int> findErrorNums(vector<int>& nums) {
        for (int i = 0; i < nums.size(); ++i) {
            while (nums[i] != i + 1 && nums[nums[i] - 1] != nums[i]) {
                int temp = nums[i];
                nums[i] = nums[nums[i] - 1];
                nums[temp - 1] = temp; 
            }
        }
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] != i + 1) {
                return {nums[i], i + 1};
            }
        }
        return {};
    }
};

(21.11.10每日一题)495. 提莫攻击

image.png
image.png
这是用数组模拟提莫的带毒攻击
模拟行为 - 图12
考虑下面两种情况,始终:

  • timeSeries[i + 1] - timeSeries[i] <= durationresult += timeSeries[i + 1] - timeSeries[i]
  • timeSeries[i + 1] - timeSeries[i] > durationresult += duration

完整代码:

class Solution {
public:
    int findPoisonedDuration(vector<int>& timeSeries, int duration) {
        if (timeSeries.size() == 0) return 0;
        int result = 0;
        int i = 0;
        for (; i < timeSeries.size() - 1; i++) {
            if (timeSeries[i + 1] - timeSeries[i] <= duration) {
                result += timeSeries[i + 1] - timeSeries[i];
            } else {
                result += duration;
            }
        }
        result += duration;
        return result;
    }
};

498. 对角线遍历

image.png
image.png
可以发现m行n列的矩阵一共有m + n - 1条对角线(数红点)
然后需要遍历m+n-1次
通过遍历次数的奇偶性区分遍历方向即可

class Solution {
public:
    vector<int> findDiagonalOrder(vector<vector<int>>& mat) {
        vector<int> res;
        int m = mat.size(), n = mat[0].size();
        int len = m + n - 1;
        for (int k = 0; k < len; k++) {
            if ((k & 1) == 0) { // 从下往上
                int i = k < m ? k : m - 1; // 左上三角从k行开始遍历 右下三角从最后一行开始遍历
                int j = k < m ? 0 : k - m + 1; // 左上三角从第一列开始遍历 右下三角从第 k - m + 1列开始
                while (i >= 0 && j < n) {
                    res.push_back(mat[i][j]);
                    i--;
                    j++;
                }
            } else {
                int i = k < n ? 0 : k - n + 1;
                int j = k < n ? k : n - 1;
                while (i < m && j >= 0) {
                    res.push_back(mat[i][j]);
                    i++;
                    j--;
                }
            }
        }
        return res;
    }
};