算法学习笔记

本文深入探讨了操作系统和算法,重点讲解了深度搜索(DFS)、广度搜索(BFS)、递归和回溯的概念与应用。在DFS中强调了搜索范围和剪枝的重要性,而在BFS中展示了队列的使用。递归的核心在于每个节点的功能实现。回溯策略中,解释了无后效性在网格路径问题中的作用,以及当上下左右都能走时如何调整解法。此外,还涵盖了位运算、双指针、滑动窗口、KMP算法、并查集、字典树(Trie)和C++库函数的相关知识。
摘要由CSDN通过智能技术生成


1. 操作系统

操作系统概括


2. 算法

2.1 深搜

  1. 可dfs元素(路径的第i个位置)
  2. 搜索范围并确定是否需要访问标记(限制dfs当前元素搜索范围)
  3. 找所有的初始dfs元素
  4. dfs当前元素:
    a. 剪枝
    b. 处理结果
    c. 在当前搜索范围中找满足条件的值,如有访问标记则修改
    d. dfs和当前节点关联的所有后续有效节点
    e. 回改当前元素和访问标记(当前节点一定不可能到达终点,若是不需要回改)

2.2 广搜

  1. 找到队列Q的起始元素,即初始搜索元素;
  2. 遍历Q中当前元素,对各元素相关联元素操作
while(Q.empty())
{
    int count = Q.size();
    while(count--)
    {
        auto e = Q.front();
        Q.pop();
        for(相关联元素)
        {
             Q.push();
        }
    }
}

2.3 递归

  1. 掌控递归函数要实现的功能:即对每个节点实现该功能,即可达到所求答案
  2. 递归函数的边界(即底部):包括结束条件和裁剪条件
    确定递归函数功能,对所有节点实现该功能既得所求
  1. 确定前、中、后序遍历
  2. 确定结果存储位置:成员变量、函数传参、函数返回值
    a. 成员变量
    b. 函数传参
    c. 函数返回值

2.4 回溯

关于回溯的访问标记是否需要回改的问题:主要不同方式到达当前位置时,其后续相关联的位置是否一致,若一致,之前在该节点向下失败,后续将不必向下,此时不需要回改,若不一致,则需要回改。
这一题是很经典的网格路径题,乍一看很简单,可以用回溯来做,但是有一个坑:如果回溯的时候把visited数组设为false,会超时。而visited不设成false,会大大提高速度,最终可以通过。
所以有第一个问题:为什么visited可以不设为false呢?
这里面其实牵涉到了动态规划的一个基本原理:无后效性。简单点说,就是我T时间到达了某个状态,在对后面要到达什么的状态进行决策的时候,和T时间之前的决策没有关系,也可以说,T时间之前的决策,对T时间之后的决策,不会产生任何影响。
这就是如果这题里面的约束条件,只能向右或向下走,产生的最关键的影响。
结合本题解释就是,不管我之前是怎么到达(row, col)这个坐标的,我在选择要走到别的格子的时候,只能向下或向右,和之前怎么走到(row, col)无关的。那么可以推出:如果我曾经经过(row, col)这个坐标,并且继续往下走得很深,但是最终失败了,那么当我再次从别的格子走到(row, col)时,也就没有必要继续往下搜了,因为它接下来还是只能往下或往右走,并且最终一定会失败,那么也就没有必要接着走到(row, col),所以也没有必要在回溯的时候,把visited[row][col]设为false了。
当然无后效性是动态规划的基本性质之一,但是它山之玉,可以攻石,这里只是借助其概念,来帮助理解回溯解法中的这个关键细节。
第二个问题:如果题目改为每个格子的上下左右都能走,那么解法会变吗?
答案是:会的。原因是如果每个格子的上下左右都能走,那么这题就不满足无后效性这个性质了,为什么呢?
其实也很好分析,假设我现在在(row, col)这个位置,并且是从上面的格子(row - 1, col)过来的,那么我从(row, col)接着走,就只能往下、左、右这三个方向,其他方向也是同理,有以下关系:
(row - 1, col) - > (row, col),上边过来, 接下来的决策方向:下、左、右
(row, col - 1) - > (row, col),左边过来, 接下来的决策方向:上、下、右
(row + 1, col) - > (row, col),下边过来, 接下来的决策方向:上、左、右
(row, col + 1) - > (row, col),右边过来, 接下来的决策方向:上、下、左
这个列表的意思就是说,(row, col)前的决策,会影响(row, col)后的决策,所以也就不满足无效性这个性质。


2.5 数学和位运算

  1. 二进制全0数:0,二进制全1数:-1
  2. c++不支持负数二进制偏移 :(unsigned int)num<<1;
  3. int 32 位 long 64位
  4. 找第k位:mark&(1<<k);
  5. 找第k位不同:(a&(1<<k)) ^ (b&(1<<k))
  6. 找正负号:(long)a>>63 & 1 若为0则正,否则负;
  7. 加法:
    int add(int a, int b)
    {
        if(b == 0) return a;
        return add(a^b, (unsigned int)(a&b)<<1);
    }
    
  8. 将目标数字修改指定位置,如奇数位:
    //互换奇偶位置
    int exchangeBits(int num) {
        int tab = 0b01010101010101010101010101010101;
        int a = (num&tab)<<1;
        int b = (num&(tab<<1))>>1;
        return a|b;
    }
     
    //i-j为0,其余为1
    int mark = -1;
    for(int k = i; k <= j; k++)
    {
        mark -= (1<<k);
    }
    
  9. 打印数字的二进制:std::cout << bitset<8>(119) << "\n";

2.6 双指针

双指针问题,一般问题复杂度不高,用两个关键指针即可解决问题,对复杂问题的子问题,熟练掌握双指针可以快速解决

  1. 同侧初始化指针:
    • 不同位置出发,有间隔,目的:不修改已更新位置的数据(合并排序数组),利用间隔距离(链表倒数k节点)
    • 同位置出发,同时移动(连接两链表,找相同节点),不同时移动,指针移动维护固定条件(最小差)
  2. 不同侧初始化指针:
    • 两指针不相关,不同侧找不同条件值,以当前位置节点为单位考虑:
      储水:当前节点最大储水(所有节点求和)(找当前节点左边最大值,右边最大值,更新最大值)
    • 部分排序:当前节点需左移/右移(求最左需右移id和最右需左移id)(需左移:从左开始遍历,左边有比当前位置大的,更新最大值)

2.7 滑动窗口

右指针右移,满足条件后,左指针右移至不满足(更新结果)

int slidingWindow(vector<int> nums) 
{
    int n = nums.size();
    int ans = 0;
    // 记录窗口内的元素及其个数,非必要
    map<int, int> um;
    // l:窗口左边界; r:窗口右边界
    int l = 0, r = 0;
    // r 指针负责探索新的区间,直到搜索到nums的某末尾
    while (r < n) 
    {
        // 右指针右移,搜索
        um[r]++;
        r++;
        //l指针右移,窗口收缩
        while(区间 [l, r] is Invalid) 
        {
            // 此处处理结果, deal with(ans, 区间[l, r])	
            res = max(ans, r - l + 1); // 或者res = min(ans, r - l + 1);
            um[l]--;
            l++;
        }
    }
    return ans;
}

2.8 KMP算法

字符串匹配KMP算法:在主串T中,找模式串P,返回主串位置
构建next数组:最大公共前后缀右移一位,首位补-1,遍历用while,方便更新id
next数组构建说明

class Solution {
public:
    int strStr(string haystack, string needle) {
        int n = needle.size();
        vector<int> next(n+1);
        getFail(needle, next);

        int i = 0, j = 0;
        while(i < haystack.size() && j < n)
        {
            if(j == -1 || haystack[i] == needle[j])
            {
                j++;
                i++;
            }
            else j = next[j];
        }
        if(j == needle.size()) return i - j;
        else return -1;
    }
    
    void find(vector<int>& ans, string& T, string& P, vector<int>& next)
    {
        int n = P.size();
        int i = 0, j = 0;
        while(i < T.size())
        {
            if(j == -1 || T[i] == P[j])
            {
                i++;
                j++;
            }
            else j = next[j];
            if(j == n) ans.push_back(i - j);
        }
    }

    void getFail(string p, vector<int>& next)
    {
        int k = -1;
        int j = 0;
        next[j] = k;
        while(j < p.size())
        {
            if(k == -1 || p[j] == p[k])
            {
                j++;
                k++;
                next[j] = k;
            }
            else k = next[k];
        }
        return;
    }
};

2.9 并查集

class Digsta {
public:
    map<string, string> parent;
    //初始化并查集所有元素
    Digsta(vector<string>& names)
    {
        for(auto& e : names)
        {
            int pos = e.find("(");
            string tmp = e.substr(0, pos);
            parent[tmp] = tmp;
        }
    }

    ~Digsta(){}

    //递归找父节点
    string find(string& x)
    {
        return parent[x] == x ? x : find(parent[x]);
    }
    
    //小集合加入并查集中,分别找到其父节点,并将父节点用parent连接
    void add(string& a, string& b)
    {
        string fa = find(a);
        string fb = find(b);
        if(fa != "" && fb != "") 
        {
            if(strcmp(fa.c_str(), fb.c_str()) > 0)
            {
                parent[fa] = fb;
            }
            else
            {
                parent[fb] = fa;
            }
        }
    }

    //额外添加元素
    void addStr(string& x)
    {
        parent[x] = x;
    }

    bool isSame(string& a, string& b)
    {
        return find(a) == find(b);
    }
};

class Solution {
public:
    vector<string> trulyMostPopular(vector<string>& names, vector<string>& synonyms) {
        Digsta setName(names);
        map<string, int> ansTmp;
        for(auto& e : synonyms)
        {
            int pos = e.find(",");
            string nameA = e.substr(1, pos - 1);
            string nameB = e.substr(pos+1, e.length()-pos-2);
            if(setName.find(nameA) == "")
            {
                setName.addStr(nameA);
            }
            if(setName.find(nameB) == "")
            {
                setName.addStr(nameB);
            }
            setName.add(nameB, nameA);
        }

        for(auto& e : names)
        {
            int pos = e.find("(");
            string nameTmp = e.substr(0, pos);
            string countStr = e.substr(pos+1, e.length()-pos-2);
            int count = atoi(countStr.c_str());
            ansTmp[setName.find(nameTmp)] += count;
        }
        vector<string> ans;
        for(auto& e : ansTmp)
        {
            string tmp = e.first + "(" + std::to_string(e.second) + ")";
            ans.push_back(tmp);
        }
        return ans;
    }
};

2.10 字典树(Trie)

class Trie {
public:
    Trie* next[26] = {nullptr};
    bool isEnd;
    Trie(){
        isEnd = false;
    }
    void insert(string& s)
    {
        Trie* cur = this;
        int n = s.length();
        for(int i = n - 1; i >= 0; i--)
        {
            int pos = s[i] - 'a';
            if(cur->next[pos] == nullptr)
            {
                cur->next[pos] = new Trie();
            }
            cur = cur->next[pos];
        }
        cur->isEnd = true;
    }
};
 
class Solution {
public:
    int respace(vector<string>& dictionary, string sentence) {        
        Trie* dicTree = new Trie();
        for(string& e : dictionary)
        {
            dicTree->insert(e);
        }
        int n = sentence.size();
        vector<int> dp(n+1, INT_MAX);
        dp[0] = 0;
        for(int i = 0; i < n; i++)
        {
            int j = i;
            Trie* cur = dicTree;
            dp[i+1] = dp[i] + 1;
            for(; j >= 0; j--)
            {
                int pos = sentence[j] - 'a';
                if(cur->next[pos] == nullptr)
                {
                    break;
                }
                else
                {
                    cur = cur->next[pos];
                }
                if(cur->isEnd)
                {
                    dp[i+1] = min(dp[j], dp[i+1]);
                    //break; 找到,且找最长匹配
                }
            }
        }
        return dp[n];
    }
};

2.11 c++库函数

//自定义比较函数
class MyComp
{
public:
    bool operator()(const pair<int, int>& a, const pair<int, int>& b)
    {
        return a.first == b.first ? a.second > b.second : a.first < b.first;
    }
};
 
class Solution {
public:
    int bestSeqAtIndex(vector<int>& height, vector<int>& weight) {
        //数组对排序
        vector<pair<int, int>> hw;
        for(int i = 0; i < height.size(); i++) hw.push_back(make_pair(height[i], weight[i]));
        sort(hw.begin(), hw.end(), MyComp());
 
        vector<int> dp;
        for(auto& [h, w] : hw)
        {
            //二分查找,已排序数组,找第一个不小于w的元素,返回迭代器
            auto p = lower_bound(dp.begin(), dp.end(), w);
            if(p == dp.end()) dp.push_back(w);
            else *p = w;
        }
        return dp.size();
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sinat_38707640

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值