leetcode《数组和字符串》的刷题笔记

这篇博客探讨了LeetCode中涉及数组和字符串的难题,包括二分查找法解决搜索插入位置、合并区间、旋转图像以及最长回文子串的问题。文章详细介绍了每种问题的解决方案,如使用排序、二分查找、翻转和动态规划等策略,并提供了相关代码示例。
摘要由CSDN通过智能技术生成

这部分笔记为leetcode《数组和字符串》的刷题笔记,过于简单的题目没有记录

35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4

提示:

1 <= nums.length <= 10^4
-10^4 <= nums[i] <= 10^4
nums 为 无重复元素 的 升序 排列数组
-10^4 <= target <= 10^4
掌握思路一(二分)
  • 题目中说必须用 log n 复杂度的算法,首先想到的就是二分
  • 题中没找到也要返回一个插入的位置,可以统一处理,找到第一个大于等于target的数的位置即可
  • 没有找到则返回末尾的位置 n 即可
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        
        int n = nums.size();
        int left = 0;
        int right = n - 1;

        while (left < right) {
            int mid = (left + right) >> 1;
            if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid;
            }

        }
        if (nums[left] < target) return n;
        return left;

    }
};
56. 合并区间
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3][2,6] 重叠, 将它们合并为 [1,6].

示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4][4,5] 可被视为重叠区间。

提示:

1 <= intervals.length <= 10^4
intervals[i].length == 2
0 <= starti <= endi <= 10^4
掌握思路一(排序)
  • 这题又让我想起了数组的题目先排序一下会不会更好做
  • 数组类型的数组排序默认按子数组的第一个元素排序
  • 熟悉 back() 的用法,快速获取到数组的最后一个元素
class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        int n = intervals.size();
        if (n == 0) return intervals;

        sort(intervals.begin(), intervals.end());   	//排序后左端点比较好判断
        vector<vector<int>> res;

        for (int i = 0; i < n; i++) {
            int L = intervals[i][0];    				//区间左端点
            int R = intervals[i][1];    				//区间右端点
            if (!res.size() || res.back()[1] < L)   	//res为空或新区间左端点比原右端点值大说明无交集
                res.push_back({L, R});              	//加入到结果集当中去
            else 
                res.back()[1] = max(res.back()[1], R);  //合并区间,更新结果集最后一个元素
        }

        return res;
    }
};
48. 旋转图像
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
    
示例 2:
输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]] 

提示:

n == matrix.length == matrix[i].length
1 <= n <= 20
-1000 <= matrix[i][j] <= 1000
掌握思路一(翻转代替旋转)
  • 旋转矩阵,由于翻转更容易实现,考虑转化成翻转来做
  • 先上下对称翻转,再对角线对称翻转
class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        
        int n = matrix.size();

        //step 1 沿着水平轴翻转
        for (int i = 0; i < n / 2; i++) 
            for (int j = 0; j < n; j++)
                swap (matrix[i][j], matrix[n - 1 - i][j]);
        
        //step 2 沿着对角线翻转
        for (int i = 0; i < n; i++) 
            for (int j = 0; j < i; j++)
                swap (matrix[i][j], matrix[j][i]);
        
    }
};
掌握思路二(辅助数组)
  • 用一个辅助数组存结果
class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();
        auto matrix_new = matrix;
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < n; ++j) 
                matrix_new[j][n - i - 1] = matrix[i][j];

        matrix = matrix_new;
    }
};
面试题 01.08. 零矩阵
编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。

示例 1:
输入:
[
  [1,1,1],
  [1,0,1],
  [1,1,1]
]
输出:
[
  [1,0,1],
  [0,0,0],
  [1,0,1]
]

示例 2:
输入:
[
  [0,1,2,0],
  [3,4,5,2],
  [1,3,1,5]
]
输出:
[
  [0,0,0,0],
  [0,4,5,0],
  [0,3,1,0]
]
掌握思路一(标记数组)
  • 记住初始化二维数组为0的写法 memset(matrix_flag, 0, sizeof(matrix_flag));
  • 官方题解的标记数组为 vector<int> row(m), col(n);
class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        int m = matrix.size();          //m行
        int n = matrix[0].size();       //n列
        int matrix_flag[m][n];			//此处不能初始化否则会保存
        memset(matrix_flag, 0, sizeof(matrix_flag));

        //flag数组判断是否要对该位置所在行列置0
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    matrix_flag[i][j] = 1;
                }
            }
        }
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix_flag[i][j] == 1) {
                    for (int k = 0; k < n; k++) matrix[i][k] = 0;
                    for (int k = 0; k < m; k++) matrix[k][j] = 0;
                }
            }
        }
    }
};
498. 对角线遍历
给你一个大小为 m x n 的矩阵 mat ,请以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。

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

示例 2:
输入:mat = [[1,2],[3,4]]
输出:[1,2,3,4]
    
提示:
m == mat.length
n == mat[i].length
1 <= m, n <= 10^4
1 <= m * n <= 10^4
-105 <= mat[i][j] <= 10^5

img

掌握思路一(遍历对角线)
  • 这题做了非常久
class Solution {
public:
    vector<int> findDiagonalOrder(vector<vector<int>>& mat) {
        vector<int> res;                    //res用来保存结果
        int m = mat.size();                 //m行
        int n = mat[0].size();              //n列
        
        //按照对角线来遍历,总共有m+n-1条对角线,第一行与最后一列可以标识出每一对角线,这里称为标识点
        //用i从0开始遍历对角线,第i条对角线的这个i正好可以表示为该对角线所有元素的下标和
        //也就是对角线上只要知道对角线和以及横纵坐标其一即可确定一点
        int i = 0;
        while (i < m + n) {
            //奇数趟,左下到右上
            int x1 = (i < m) ? i : m - 1; //确定初始点
            int y1 = i - x1;
            while (x1 >= 0 && y1 < n) {
                res.push_back(mat[x1][y1]);
                x1--;   //左下到右上,行减列增
                y1++;   //一减一加维持在同一对角线
            }
            i++;

            if (i > m + n - 1) break;
            //偶数趟,右上到左下
            int y2 = (i < n) ? i : n - 1;
            int x2 = i - y2; 
            while (y2 >= 0 && x2 < m) {
                res.push_back(mat[x2][y2]);
                x2++;
                y2--;
            }
            i++;
        }
    return res;
    }
};
5. 最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
    
示例 2:
输入:s = "cbbd"
输出:"bb" 

提示:
1 <= s.length <= 1000
s 仅由数字和英文字母组成
掌握思路一(暴力循环)
  • 由于字符串长度小于1000,因此我们可以用 O(n^2) 的算法枚举所有可能的情况
  • 首先枚举回文串的中心 i,然后分两种情况(回文串长度为奇数/偶数)向两边扩展边界,直到遇到不同字符为止
class Solution {
public:
    string longestPalindrome(string s) {
        int res_len = 0;
        string res;
        int n = s.size();

        for (int i = 0; i < n; i++) {
            //回文串是奇数长度,j表示向外扩展的长度
            //从第i个位置逐渐向外扩展j个长度
            for (int j = 0; i - j >= 0 && i + j < n; j++) {
                if (s[i - j] == s[i + j]) {
                    //扩展的位置符合回文串的条件,判断此时长度是否最大
                    if (2 * j + 1 > res_len) {
                        //最大则要更新结果串
                        res_len = 2 * j + 1;
                        res = s.substr(i - j, res_len);
                    }
                }
                else break; //不满足回文串的定义直接退出该点的拓展循环
            }
			
            //回文串是偶数长度,j和k表示向外扩展的位置
            for (int j = i, k = i + 1; j >= 0 && k < n; j--, k++) {
                if (s[j] == s[k]) {
                    //扩展的位置符合回文串的条件,判断此时长度是否最大
                    if (k - j + 1 > res_len) {
                        res_len = k - j + 1;
                        res = s.substr (j, res_len);
                    }
                }
                else break;
            }
        }

        return res;
    }
};
掌握思路二(动态规划)
  • 回顾DP 问题一般步骤
    1. 定义dp数组中的dp[i];
    2. 找dp数组间的关系式(核心
    3. 写出不能由关系式计算得到的初始边界值
  • 这题的dp思路为
    1. 定义dp数组中的dp[i]:这里要用二维,dp[i][j]表示字符串s的第i到j个字母组成的串是否为回文串,字串为回文串,自身两端相等则为true
    2. 找dp数组间的关系式:dp[i][j] = dp[i + 1][j - 1] && s[i] == s[j]
    3. 写出不能由关系式计算得到的初始边界值:dp[i][i] = truedp[i][i + 1] = s[i] == s[i + 1]
class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();
        if (n < 2) {
            return s;
        }

        int maxLen = 1;
        int begin = 0;
        // dp[i][j] 表示 s[i..j] 是否是回文串
        vector<vector<int>> dp(n, vector<int>(n));
        // 初始化:所有长度为 1 的子串都是回文串
        for (int i = 0; i < n; i++) {
            dp[i][i] = true;
        }
        // 递推开始
        // 先枚举子串长度
        for (int L = 2; L <= n; L++) {
            // 枚举左边界,左边界的上限设置可以宽松一些
            for (int i = 0; i < n; i++) {
                // 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
                int j = L + i - 1;
                // 如果右边界越界,就可以退出当前循环
                if (j >= n) {
                    break;
                }

                if (s[i] != s[j]) {
                    dp[i][j] = false;
                } else {
                    if (j - i < 3) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }

                // 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
                if (dp[i][j] && j - i + 1 > maxLen) {
                    maxLen = j - i + 1;
                    begin = i;
                }
            }
        }
        return s.substr(begin, maxLen);
    }
};
151. 颠倒字符串中的单词
给你一个字符串 s ,颠倒字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。 

示例 1:
输入:s = "the sky is blue"
输出:"blue is sky the"

示例 2:
输入:s = "  hello world  "
输出:"world hello"
解释:颠倒后的字符串中不能存在前导空格和尾随空格。

示例 3:
输入:s = "a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,颠倒后的字符串需要将单词间的空格减少到仅有一个。

提示:
1 <= s.length <= 10^4
s 包含英文大小写字母、数字和空格 ' '
s 中 至少存在一个 单词
掌握思路一(数组翻转)
  • 将每个单词存入数组中,翻转该数组,再用结果串按数组顺序存储所有单词即可
class Solution {
public:
    string reverseWords(string s) {
        if (!s.size()) return s;
        vector<string> v;
        int n = s.size();
        string res;

        int i = 0;
        while (i < n) {
            string temp;
            while(i < n && isalnum(s[i])) {				//存入每个单词到数组中
                temp += s[i];
                i++;
            }
            if (temp.size()) v.push_back(temp);
            i++;
        }
        reverse (v.begin(), v.end());					//翻转该数组

        for (int i = 0; i < v.size(); i++) {
            res += v[i];
            if (i != v.size() - 1) res += " ";			//最后一个单词不用空格
        }

        return res;
    }
};
28. 实现 strStr()
实现 strStr() 函数。

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回  -1 。

说明:

当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。

对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。

示例 1:
输入:haystack = "hello", needle = "ll"
输出:2
    
示例 2:
输入:haystack = "aaaaa", needle = "bba"
输出:-1
    
示例 3:
输入:haystack = "", needle = ""
输出:0
 
提示:

1 <= haystack.length, needle.length <= 10^4
haystack 和 needle 仅由小写英文字符组成
掌握思路一(find函数)
  • 投机取巧,用 find() 函数
class Solution {
public:
    int strStr(string haystack, string needle) {
        return haystack.find(needle); 
    }
};
掌握思路二(暴力做法)
  • 挨个判断即可
class Solution {
public:
    int strStr(string haystack, string needle) {
        int n = haystack.size(), m = needle.size();
        for (int i = 0; i + m <= n; i++) {
            bool flag = true;
            for (int j = 0; j < m; j++) {
                if (haystack[i + j] != needle[j]) {
                    flag = false;
                    break;
                }
            }
            if (flag) {
                return i;
            }
        }
        return -1;
    }
};
掌握思路三(KMP算法)
  • 在熟练掌握KMP算法之前,就先用暴力去做吧
27. 移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

示例 1:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
    
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

提示:

0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100
掌握思路一(快慢指针)
  • 快指针找到不为val的值赋给慢指针即可
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int n = nums.size();
        int slow = 0;
        for (int fast = 0; fast < n; fast++) {
            if (nums[fast] != val) {
                nums[slow] = nums[fast];
                slow++;
            }
        }
        return slow;
    }
};
掌握思路二(删除法)
  • 回顾删除的写法,计数器记录删除个数
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int n = nums.size();
        auto it = nums.begin();

        int cnt = 0;
        while (it != nums.end()) {
            if (*it == val) {               //*it == val
                cnt++;
                it = nums.erase(it);		//要特别注意这种写法,删除后迭代器指向的是原来删除元素的后一个元素,所以不要多加it++
            } else {
                it++;
            }
        }
        return n - cnt;
    }
};
209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

 

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:

输入:target = 4, nums = [1,4,4]
输出:1
示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
 

提示:

1 <= target <= 10^9
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^5
掌握思路一(双指针)
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n = nums.size();
        int res = INT_MAX;
        // int max_sum = 0;

        for (int i = 0; i < n; i++) {
            int len = 0;
            int sum = 0;
            for (int j = i; j < n; j++) {
                if (sum < target) {
                    sum += nums[j];
                    len++;
                }
                if (sum >= target) {
                    res = min (res, len);
                    break;
                }
                if (j == n - 1) break;
            }
            // max_sum = max (max_sum, sum);
        }
        // if (max_sum < target) return 0;
        if (res == INT_MAX) return 0;
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值