力扣杂题集合【哈希 双指针 字符串 KMP DFS】

这篇文章介绍了几个与IT技术相关的题目,包括哈希表的应用、双指针算法在数组操作中的运用(如三数之和、四数之和、找出子串和IP地址恢复),以及解决数独问题的方法。
摘要由CSDN通过智能技术生成

杂题集合 (哈希 双指针 字符串 KMP DFS)

454. 四数相加Ⅱ

从 A, B, C, D 中各挑一个数满足 a + b + c + d = 0,求可能的组合个数。1 ≤ n ≤ 500

根据数据量判断只能用 O(n2logn) 及以下的时间复杂度,典型的空间换时间问题,用一个哈希表预处理 C + D 之和。

int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
    unordered_map<int, int> mp;

    for (auto c : C)
        for (auto d : D)
            mp[c + d] ++;

    int res = 0;

    for (auto a : A)
        for (auto b : B)
            res += mp[-(a + b)];

    return res;
}

15. 三数之和 18. 四数之和

一个数组中选择不同i, j, k 使 nums[i] + nums[j] + nums[k] = 0,求所有的组合方案。3 ≤ n ≤ 3000

多个数的问题,基本上是要定1个或者2个数,也就是枚举这些数,剩下的看有没有性质。

本题是典型双指针的运用,但是需要注意如何去除重复情况,如[1, 1, 1, -2]只能算作1种。

vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>> res;
    int n = nums.size();
    sort(nums.begin(), nums.end());

    for (int i = 0; i < n; i ++) 
    {
        if (i && nums[i] == nums[i - 1]) continue;
        int l = i + 1, r = n - 1;
        while (l < r)
        {
            int sum = nums[i] + nums[l] + nums[r];
            if (sum < 0) l ++;
            else if (sum > 0) r --;
            else
            {
                res.push_back({nums[i], nums[l], nums[r]});
                l ++, r --;
                while (l < r && nums[l] == nums[l - 1]) l ++;
                while (l < r && nums[r] == nums[r + 1]) r --; 
            }
        }
    }
    return res;
}

28. 找出字符串中第一个匹配项下标

字符串Hash
unsigned long long p[10010], h[10010];
int P = 131;

unsigned long long query(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

int strStr(string s, string t) {
    int n = s.length(), m = t.length(); s = " " + s, t = " " + t;

    p[0] = 1;
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = p[i - 1] * P;
        h[i] = h[i - 1] * P + s[i];
    }    

    unsigned long long target = 0;
    for (int i = 1; i <= m; i ++ ) target = target * P + t[i];

    for (int l = 1; l + m - 1 <= n; l ++ )
    {
        int r = l + m - 1;
        if (query(l, r) == target) return l - 1;
    }

    return -1;
}
KMP AcWing 831. KMP字符串

找到所有原串 s 中和模板串 t 相匹配的子串的起始下标。

KMP 习惯上下标从 1 开始。

next[i]: 模板串[1 ~ i] 的子串,前后缀相同的最长子串的长度。

举个例子直观理解,模板串 t = "abababab"

next[1] = 0next[1] 为特例,直接设定成 0

next[2] = 0, next[3] = 1, next[4] = 2, next[5] = 3, next[6] = 4, next[7] = 5, next[8] = 6

我们以 next[5] 为例,子串为 ababa,最长的相同前后缀的就是 aba

匹配过程中都是 p[j + 1]s[i] 比较,如果相等那就比较两个串下一个字符,如果不相等,那 j 就跳到 next[j] 的位置。

原串 s = "abababc" 模板串 t = "abababab"

i = 7 的时候, j + 1 = 7s[i] != s[j + 1] 此时应该 j = next[j]

那为什么能够这样跳转呢,因为 s[1 ~ 6] 和模板串的 t[1 ~ 6] 相等,同时 next[6] = 4 这意味着模板串以 s[6] 结尾的子串 ababab 其最长匹配的前后缀为 abab。因此,由于 s[3 ~ 6] == t[3 ~ 6] 同时 t[1 ~ 4] == t[3 ~ 6],故 t[1 ~ 4] == s[3 ~ 6] 因此可以避免从头开始匹配模板串,直接 j 指针从 j + 1 = 5 的位置和 i = 7 比较。

int n, m;
string s, p;
int ne[N];

int main()
{
    cin >> m >> p >> n >> s;
    p = " " + p, s = " " + s;
    
    // 构造next数组过程
    for (int i = 2, j = 0; i <= m; i ++ )
    {
        while (j && p[i] != p[j + 1]) j = ne[j];
        if (p[i] == p[j + 1]) j ++;
        ne[i] = j;
    }
    
    // 匹配过程
    for (int i = 1, j = 0; i <= n; i ++ )
    {
        while (j && s[i] != p[j + 1]) j = ne[j];
        if (s[i] == p[j + 1]) j ++;
        if (j == m) // 成功匹配
        {
            cout << i - m + 1 - 1 << " "; // 要求原串从下标0开始
            j = ne[j];
        }
    }
    
    return 0;
}

459. 重复的子字符串

找字符串 s 是否存在一个循环节。

KMP 找循环节的经典问题,KMP 有以下2个结论需要记住:

(1) n - next[n] 的含义就是该字符串的最小周期,但是可能最后一段不完整;

(2) 如果周期可以整除 n,那最小周期一定是其他周期的约数,也就是说如果最小周期不能整除 n,那一定没有周期;

bool repeatedSubstringPattern(string s) {
    int n = s.size(); s = " " + s;
    vector<int> ne(n + 1);

    for (int i = 2, j = 0; i <= n; i ++ )
    {
        while (j && s[i] != s[j + 1]) j = ne[j];
        if (s[i] == s[j + 1]) j ++;
        ne[i] = j;
    }

    int t = n - ne[n];
    return t < n && n % t == 0;
}

151. 反转字符串中的单词

反转字符串中的单词顺序, " the sky is blue " → “blue is sky the”。

空间复杂度要求O(1)

  1. 去除多余空格,把单词移到字符串前面;2. 反转整个字符串;3.反转每个单词内部顺序。

    其中第2步和第3步可以互换。

string reverseWords(string s) {
    int k = 0;
    for (int i = 0; i < s.length(); i ++ )
    {
        if (s[i] == ' ') continue;
        int t = k, j = i;
        while (j < s.length() && s[j] != ' ') s[t ++] = s[j ++];
        reverse(s.begin() + k, s.begin() + t);
        s[t ++] = ' ';
        k = t, i = j;
    }

    if (k) k --; // 去掉最后一个单词后面的空格
    s.erase(s.begin() + k, s.end());
    reverse(s.begin(), s.end());

    return s;
}

40. 组合总和 Ⅱ

找出 nums 中所有可以使数字和为 target 的组合,每个数字只能使用一次 ,解集不能包含重复的组合

nums = [10, 1, 2, 7, 6, 1, 5], target = 8

res = {{1, 1, 6}, {1, 2, 5}, {1, 7}, {2, 6}}

难点在于如何去除重复的组合,首先要明确会出现哪些重复组合(nums 先排好序):

在这里插入图片描述

本题是允许树枝重复{1, 1, 6},不允许树层重复。

void dfs(int sum, int start, vector<int> nums, int target) {
    if (sum >= target) {
        if (sum == target) res.push_back(ans);
        return;
    }
    for (int i = start; i < nums.size(); i ++ ) {
        if (i && nums[i] == nums[i - 1] && !st[i - 1]) continue;
        ans.push_back(nums[i]);
        st[i] = true;
        dfs(sum + nums[i], i + 1, nums, target);
        ans.pop_back();
        st[i] = false;
    }
}

vector<vector<int>> combinationSum2(vector<int>& nums, int target) {
    sort(nums.begin(), nums.end());
    dfs(0, 0, nums, target);
    return res;
}

90. 子集Ⅱ

对应每个物品选不选的问题,可能有多个重复物品,要求输出不重复的子集组合方案。

也就是对于 [1, 2, 2] 选择 [1, 2][1, 2] 是等价的。

舍弃之前每个物品选或不选的思想,改成只选物品,但是每次只能从 start 之后开始选。

void dfs(int start, vector<int> nums) {
    res.push_back(ans);
    if (start == nums.size()) return;
    
    for (int i = start; i < nums.size(); i ++ ) {
        if (i && nums[i] == nums[i - 1] && !st[i - 1]) continue; // 去除树层重复
        st[i] = true;
        ans.push_back(nums[i]);
        dfs(i + 1, nums);
        st[i] = false;
        ans.pop_back();
    }
}

vector<vector<int>> subsetsWithDup(vector<int>& nums) {
    sort(nums.begin(), nums.end());
    dfs(0, nums);
    return res;
}

491. 非递减子序列

要求输出一个序列中所有的非递减子序列。

由于本题求的是子序列,因此不能对数组进行排序,因此和之前的去重逻辑不同。

在每一层开一个哈希表,记录被搜索过的元素即可。

void dfs(int start, int last, vector<int> nums) {
    if (path.size() >= 2) res.push_back(path);
    if (start == nums.size()) return;
    unordered_set<int> s;

    for (int i = start; i < nums.size(); i ++ ) {
        if (nums[i] < last || s.count(nums[i])) continue;
        s.insert(nums[i]);
        path.push_back(nums[i]);
        dfs(i + 1, nums[i], nums);
        path.pop_back();
    }
}

vector<vector<int>> findSubsequences(vector<int>& nums) {
    dfs(0, -110, nums);
    return res;
}

131. 切割回文子串

给定一个串,将其进行分割,使得每个子串都是回文子串。

bool isValid(string s) {
    string tmp = s;
    reverse(tmp.begin(), tmp.end());
    return s == tmp;
}

void dfs(int last, string s) {
    if (last == s.length()) {
        res.push_back(ans);
        return;
    }
    for (int len = 1; len + last - 1 < s.length(); len ++ ) {
        string cur = s.substr(last, len);
        if (isValid(cur)) {
            ans.push_back(cur);
            dfs(len + last, s);
            ans.pop_back();
        }
    }
}

vector<vector<string>> partition(string s) {
    dfs(0, s);
    return res;
}

93. 复原IP地址

给定一个字符串,返回其对应的所有可能IP地址。

bool isValid(string s) {
    if (s[0] == '0') {
        if (s.length() == 1) return true;
        else return false;
    }
    return stoi(s) <= 255;
}

void dfs(int u, int last, string ans, string s) {
    if (u == 4) {
        res.push_back(ans);
        return;
    }

    if (u == 3) {
        int len = s.length() - last;
        if (len > 3 || len == 0) return;
        string cur = s.substr(last, len);
        if (isValid(cur)) {
           string tmp = ans + cur;
           dfs(u + 1, last, tmp, s);
        }
    } else {
        for (int len = 1; last + len - 1 < s.length() && len <= 3; len ++ ) {
            string cur = s.substr(last, len);
            if (isValid(cur)) {
                string tmp = ans + cur + ".";
                dfs(u + 1, last + len, tmp, s);
            }
        }
    }
}

vector<string> restoreIpAddresses(string s) {
    if (s.length() > 12) return {};
    dfs(0, 0, "", s);
    return res;
}

37. 解数独

bool col[9][9], row[9][9], cell[3][3][9];

bool dfs(int x, int y, vector<vector<char>> &board) {
    if (y == 9) x ++, y = 0;
    if (x == 9) return true;
    if (board[x][y] != '.') return dfs(x, y + 1, board);

    for (char c = '1'; c <= '9'; c ++ ) {
        int i = c - '1';
        if (row[x][i] || col[y][i] || cell[x / 3][y / 3][i]) continue;
        row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = true;
        board[x][y] = c;
        if (dfs(x, y + 1, board)) return true;
        row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = false;
        board[x][y] = '.';
    }

    return false;
}

void solveSudoku(vector<vector<char>>& board) {
    for (int i = 0; i < board.size(); i ++ ) 
        for (int j = 0; j < board[0].size(); j ++ ) 
            if (board[i][j] != '.') {
                int num = board[i][j] - '1';
                row[i][num] = col[j][num] = cell[i / 3][j / 3][num] = true;
            }

    dfs(0, 0, board);
}
  • 30
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值