数据结构与算法题型模板(随缘更新)

20. 有效的括号

题目描述

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。

有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。

  • 左括号必须以正确的顺序闭合。

注意空字符串可被认为是有效字符串。

示例数据

示例 1:

输入: "()"
输出: true

示例 2:

输入: "()[]{}"
输出: true

示例 3:

输入: "(]"
输出: false

示例 4:

输入: "([)]"
输出: false

示例 5:

输入: "{[]}"
输出: true

题解

看一下这个图我觉得就OK啦,仔细看,瞪大眼!

使用unordered_map来进行入栈和出栈

判断某个键是否存在:

1、使用find

map.find(key) == map.end()

2、使用count

如果key存在,则count返回1,如果不存在,则count返回0.

pairs.count(ch)

20.gif

class Solution {
public:
    bool isValid(string s) {
        int n = s.size();
        if (n % 2 == 1) {
            return false;
        }
		//使用了哈希表来判断是否能够形成括号,从而决定进行入栈操作还是出栈操作。
        //左边就入栈,右边就出栈
        unordered_map<char, char> pairs = {
            {')', '('},
            {']', '['},
            {'}', '{'}
        };
        stack<char> stk;
        for (char ch: s) {
            //看看是不是右半边,是的话就出栈
            if (pairs.count(ch)) {
                if (stk.empty() || stk.top() != pairs[ch]) {
                    return false;
                }
                stk.pop();
            }
            //是左边就入栈
            else {
                stk.push(ch);
            }
        }
        return stk.empty();
    }
};

队列

题目描述

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例数据

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

题解

很标准的滑动窗口法

此题主要有两个比较巧妙的地方:

1.while处

2.if处

解释见代码中注释。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
		//用来存结果的vector
        vector<int> res;
        //创建队列
        deque<int> tem;     
        //开始逐一遍历
        for(int i=0; i<nums.size(); i++)
        {
            //while这步操作是为了让当前窗口最大值的索引值放到tem的队头
            while(!tem.empty() && nums[i]>nums[tem.back()])
            {
                tem.pop_back();
            }
            //这里的if判断条件中的第二个是为了判断队头是否过期,也就是说队头的索引是否小于当前索引
            //往左走k步,+1是因为索引是从0开始,若当前滑窗索引便把它清理掉。
            if(!tem.empty() && tem.front()<i-k+1) tem.pop_front();
            //放入元素i
            tem.push_back(i);
            if(i>=k-1)  res.push_back(nums[tem.front()]);
        }
        return res;
    }
};

暴力法

暴力往往就是模拟的过程,不用考虑优化,不考虑时间空间复杂度,用最直觉的方式先想出一个算法思想来。

但是不要无脑直接for,暴力也是有技巧的,往往一个简单的优化,就可以得到质的进步.

我们不能无脑暴,要暴出精彩,暴出活力💝

题目描述

编写一个程序判断给定的数是否为丑数。

丑数就是只包含质因数 2, 3, 5正整数

示例数据

示例 1:

输入: 6
输出: true
解释: 6 = 2 × 3

示例 2:

输入: 8
输出: true
解释: 8 = 2 × 2 × 2

示例 3:

输入: 14
输出: false 
解释: 14 不是丑数,因为它包含了另外一个质因数 7。

说明:

  • 1 是丑数。
  • 输入不会超过 32 位有符号整数的范围: [−231, 231 − 1]。

题解

还有啥,直接暴力就完了

悄咪咪的说,我看了有人用回溯的,我还没学呢,等会吧

class Solution {
public:
    bool isUgly(int num) {
        if (num<1) return false;
        while (num%2==0) num/=2;
        while (num%3==0) num/=3;
        while (num%5==0) num/=5;
        if (num==1) return true;
        else return false;
    }
};

二分法

35. 搜索插入位置

题目描述

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

你可以假设数组中无重复元素。

示例数据

示例 1:

输入: [1,3,5,6], 5
输出: 2

示例 2:

输入: [1,3,5,6], 2
输出: 1

题解

典型的二分查找问题

设置循环条件,当左边的索引小于等于右边继续循环

1、关于mid取值的思考:

注意int(2.5)的值是5,想了想,感觉mid取值的影响不大,就一直选择靠左边的那个元素就可

网上也有使用下面写法的

int mid = left + (right - left) / 2;

2、为什么最后return 的是right呢?

其实返回的结果是left或者right都可以,没影响呀,最后结束的条件是left>right,

因为是对mid运算,其实跳出循环是left==right,因此都一样的

3、测试代码

#include<bits/stdc++.h>
using namespace std;

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int l=-1;//左边的下标
        int r=nums.size();//右边的下标
        while(l+1<r){
            int mid=(l+r)/2;
            if(nums[mid]>=target){
                r=mid;
            }else{
                l=mid;
            }
        }
        return r;
    }
};


int main()
{
    Solution solution;

    vector<int> nums;
    int data[]={1,3,5,6};
    for(int i=0;i<4;i++)
        nums.push_back(data[i]);
    cout<<solution.searchInsert(nums,2)<<endl;
    return 0;
}

贪心

贪心不是越多越好,而是当前最优解最好嗷

55. 跳跃游戏

题目描述

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个位置。

示例数据

示例 1:

输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。

示例 2:

输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。

题解

读完两遍题,很懵,淦,再读第三遍,嗷,我懂了

其实就是问能不能跳过去,很猥琐的就是有0,然后就完蛋蛋,但是可以一次跳好几下,跳过这个0

其实问题就是你最远能跳到哪,就贪心嘛,每到一个位置就更新一下最远位置,取最大值,看看能不能跳过去

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int n = nums.size();
        //能够跳到最右边的距离
        int rightmost = 0;
        for (int i = 0; i < n; ++i) {
            if (i <= rightmost) {
                rightmost = max(rightmost, i + nums[i]);
                if (rightmost >= n - 1) {
                    return true;
                }
            }
        }
        return false;
    }
};

53. 最大子序和

题目描述

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例数据

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

题解

若当前指针所指元素之前的和小于0,则丢弃当前元素之前的数列

class Solution:
    def maxSubArray(self,nums):
        if not nums:
            return -2147483648
        #初始化列表的第一个元素,cur_max是当前最大值,max_sum是整个数列的最大值
        cur_sum=max_sum=nums[0]
        for i in range(1,len(nums)):
            cur_sum=max(nums[i],cur_sum+nums[i])
            max_sum=max(cur_sum,max_sum)
        return max_sum

hdu1257.E - 最少拦截系统

http://acm.hdu.edu.cn/showproblem.php?pid=1257

题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超

过前一发的高度.某天,雷达捕捉到敌国的导弹来袭.由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹.

怎么办呢?多搞几套系统呗!你说说倒蛮容易,成本呢?成本是个大问题啊.所以俺就到这里来求救了,请帮助计算一下最少需要多少套拦截系统.

示例数据

输入:8 389 207 155 300 299 170 158 65
输出:2
解释:

题解

经典的拦截系统问题, 这道题目的最优做法应该是贪心,还可以用动态规划,其实就是求一下最长上升子序列的个数

对于贪心问题, 我们先从思维上下手, 考虑我们需要最少的系统, 那么就一定要使得每个系统都最大限度的物尽其用

然后我们再思考, 由于顺序无法更改, 一个很高的导弹我们必然要为之设置系统, 我们不如直接记录下每个系统的最低高度,

然后依次遍历导弹, 把每个导弹都放入最低高度最小的, 如果都放不下, 则开辟新的系统

#include<iostream>
#include <cstring>
#include <cstdio>
using namespace std;

struct node
{
    int high=0;
    int sum=0;
};

node a[1000];

int main()
{
    int n;
    while(cin>>n)
    {
        for(int i=0;i<n;i++)
        {
            cin>>a[i].high;
            a[i].sum=1;
        }
        for(int i=1;i<n;i++)
        {
            for(int j=0;j<i;j++)
            {
                if(a[i].high>a[j].high && a[j].sum+1>a[i].sum)  a[i].sum=a[j].sum+1;//更新数量
            }
        }
        int max1=0;
        for(int i=0;i<n;i++)
        {
            max1=(max1>a[i].sum)?max1:a[i].sum;
        }
        cout<<max1<<endl;
    }
    return 0;
}

分治

169. 多数元素

题目描述

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例数据

示例 1:

输入: [3,2,3]
输出: 3

示例 2:

输入: [2,2,1,1,1,2,2]
输出: 2

题解

如果数 a 是数组 nums 的众数,如果我们将 nums 分成两部分,那么 a 必定是至少一部分的众数。

这样以来,我们就可以使用分治法解决这个问题:将数组分成左右两部分,分别求出左半部分的众数 a1 以及右半部分的众数 a2,随后在 a1 和 a2 中选出正确的众数。

我们使用经典的分治算法递归求解,直到所有的子问题都是长度为 1 的数组。

长度为 1 的子数组中唯一的数显然是众数,直接返回即可。如果回溯后某区间的长度大于 1,我们必须将左右子区间的值合并。

如果它们的众数相同,那么显然这一段区间的众数是它们相同的值。否则,我们需要比较两个众数在整个区间内出现的次数来决定该区间的众数。

class Solution {
    int count_in_range(vector<int>& nums, int target, int lo, int hi) {
        int count = 0;
        for (int i = lo; i <= hi; ++i)
            if (nums[i] == target)
                ++count;
        return count;
    }
    int majority_element_rec(vector<int>& nums, int lo, int hi) {
        if (lo == hi)
            return nums[lo];
        int mid = (lo + hi) / 2;
        int left_majority = majority_element_rec(nums, lo, mid);
        int right_majority = majority_element_rec(nums, mid + 1, hi);
        if (count_in_range(nums, left_majority, lo, hi) > (hi - lo + 1) / 2)
            return left_majority;
        if (count_in_range(nums, right_majority, lo, hi) > (hi - lo + 1) / 2)
            return right_majority;
        return -1;
    }
public:
    int majorityElement(vector<int>& nums) {
        return majority_element_rec(nums, 0, nums.size() - 1);
    }
};

215. 数组中的第K个最大元素

题目描述

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例数据

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

题解

class Solution {
    int count_in_range(vector<int>& nums, int target, int lo, int hi) {
        int count = 0;
        for (int i = lo; i <= hi; ++i)
            if (nums[i] == target)
                ++count;
        return count;
    }
    int majority_element_rec(vector<int>& nums, int lo, int hi) {
        if (lo == hi)
            return nums[lo];
        int mid = (lo + hi) / 2;
        int left_majority = majority_element_rec(nums, lo, mid);
        int right_majority = majority_element_rec(nums, mid + 1, hi);
        if (count_in_range(nums, left_majority, lo, hi) > (hi - lo + 1) / 2)
            return left_majority;
        if (count_in_range(nums, right_majority, lo, hi) > (hi - lo + 1) / 2)
            return right_majority;
        return -1;
    }
public:
    int majorityElement(vector<int>& nums) {
        return majority_element_rec(nums, 0, nums.size() - 1);
    }
};

位运算、排序、树

DFS与BFS

104. 二叉树的最大深度

题目描述

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例数据

给定二叉树 [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7

返回它的最大深度 3 。

题解

解法一:DFS

使用递归,其实也是二叉树排序用的一种方式

首先判断这个节点是不是nullptr,如果是就返回0,不是就继续往下递归,得到最大的深度

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == nullptr) 
            return 0;
        int l = maxDepth(root->left) + 1;
        int r = maxDepth(root->right) + 1;
        return l > r ? l : r;
    }
};

解法二:BFS

先把每一个爸爸(root)层入队,然后再从前往后把爸爸层出队,儿子层再入队

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == nullptr) 
            return 0;
        queue<TreeNode*> q;
        q.push(root);
        int depth = 0;
        while(!q.empty())
        {
            //计算父亲层
            int num = q.size();
            while(num > 0)
            {
				TreeNode* p = q.front();
                //父亲出队
                q.pop();
                if(p->left)
                    //左儿子入队
                    q.push(p->left);
                if(p->right)
                    //右儿子入队
                    q.push(p->right); 
                num--;
            }
            //深度加1
            depth++;
        }
        return depth;
    }
};

hdu1241.B - Oil Deposits(dfs模板)

传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1241

题目描述

GeoSurvComp地质调查公司负责探测地下石油储量。GeoSurvComp一次处理一个大的矩形土地区域,并创建将土地分割为多个正方形地块的栅格。然后,它分别分析每一块地块,使用传感设备确定该地块是否含有石油。含有石油的地块被称为口袋。如果两个凹陷相邻,那么它们就是同一矿藏的一部分。石油储量可能相当大,可能包含许多口袋。您的工作是确定网格中包含了多少不同的石油矿藏。

输入格式

输入文件包含一个或多个网格。每个网格以一条包含m和n(网格中的行数和列数)的线开始,由单个空格分隔。如果m=0,则表示输入结束;否则,1≤m≤100,1≤n≤100。紧随其后的是m行,每行n个字符(不包括行尾字符)。每个字符对应一个地块,或者是‘*’,代表没有油,或者是‘@’,代表一个油袋。

输出格式

输出多少种相连的油袋

输入样例

1 1
*
3 5
*@*@*
**@**
*@*@*
1 8
@@****@*
5 5 
****@
*@@*@
*@**@
@@@*@
@@**@
0 0 

输出样例

0
1
2
2

解题思路

①输入数据

②制定dfs函数

③对每个点进行dfs

程序代码

#include<iostream>
#include<cstring>
using namespace std;

int m,n;
int dir[8][2]={1,0, 1,1, 0,1, -1,0, 0,-1, -1,-1, 1,-1, -1,1};//方向数组
char ch[100][100];
bool vis[100][100];

void dfs(int x, int y)
{
    for(int i=0;i<8;i++)
    {
        int new_x=x+dir[i][0];
        int new_y=y+dir[i][1];
        if(new_x>=0 && new_x<m && new_y>=0 && new_y<n && ch[new_x][new_y]=='@' && !vis[new_x][new_y])//判断筛选条件
        {
        	vis[new_x][new_y]=1;//搜过了,要标记
            dfs(new_x,new_y);
        }
    }
}


int main()
{
    while(cin>>m>>n,m||n)
    {
        int ans = 0;
        memset(vis,0,sizeof(vis));
        memset(ch,0,sizeof(ch));
        for(int i=0;i<m;i++)
            cin>>ch[i];
        /*for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
               cout<<ch[i][j]<<endl;
            }
        }*/
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {

                if(ch[i][j]=='@'&&!vis[i][j])//因为递归会标记对应的位置
                {
                    dfs(i,j);
                    ans++;
                }
            }
        }
        cout<<ans<< endl;
    }
    return 0;
}
N皇后的解的个数(占坑)
class Solution {
public:
    int x[10];
    int ans;
    int n;
    vector<vector<string>> solveNQueens(int t) {
        this->n=t;
		ans=0;
		dfs(1);
		cout<<ans<<endl;
    }
    bool judge(int t){
        for(int i=1;i<t;i++)
            if ((abs(t-i)==abs(x[i]-x[t])) || (x[i] == x[t])) //斜率相等或者行号相等
                return false;
        return true;
    }
    void dfs(int t){
        if(t>n)  ans++;
        else{
            for (int i=1; i<=n; i++){
                x[t] = i;
                if(judge(t)) dfs(t+1);
            }
        }
    }
};

查并集

547. 朋友圈

题目描述

班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。

给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。

示例数据

示例 1:

输入:
[[1,1,0],
 [1,1,0],
 [0,0,1]]
输出:2 
解释:已知学生 0 和学生 1 互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回 2 。

示例 2:

输入:
[[1,1,0],
 [1,1,1],
 [0,1,1]]
输出:1
解释:已知学生 0 和学生 1 互为朋友,学生 1 和学生 2 互为朋友,所以学生 0 和学生 2 也是朋友,所以他们三个在一个朋友圈,返回 1 。

提示:

  • 1 <= N <= 200
  • M[i][i] == 1
  • M[i][j] == M[j][i]

题解

标准模板,先初始化数据,自己的祖先开始是自己,然后双层循环,对N个学生两两做判断

Union函数使用findFather来查祖先a和b的祖先,如果祖先一致就不管了,如果不一致就设置为同一个祖先

findFather函数,首先判断是不是一个代表,不是的话就设置成一个代表,

关于为啥用while,群里水友说要查询到每个的根节点,把并查集当树看,可以不止两层,可以是三层四层,两个初试并查集合并之后层数会增高

真的拗不过来这个弯,开学再问问学长吧

class Solution {
public:
    int father[210];
    //最开始的时候,每个节点时分散的,都是自己的祖先
    void init()
    {
        for(int i=0;i<210;i++)
        {
            father[i] = i;
        }
    }
    /*
    未进行路径压缩,爆timeout
    int findFather(int x)
    {
        while(x!=father[x])
        {
            x = father[x];
        }
        return x;
    }
    */
	//查找祖先节点,当节点记录的祖先是自己,则表示查找到祖先了
    int findFather(int x)
    {
        int p,tmp;
        p=x;
        while(x!=father[x])
        {
            x = father[x];
        }
        while(p!=x)
        {
            tmp=father[p];
            father[p]=x;//路径压缩
            p=tmp;
        }
        return x;
    }
    //合并节点:设置共同祖先
    void Union(int a,int b)
    {
        int fa = findFather(a);
        int fb = findFather(b);
        if(fa!=fb)
        {
            father[fa] = fb;
        }
    }
    
    //主函数
    int findCircleNum(vector<vector<int>>& M) {
        init();
        //对N个学生两两做判断
        for(int i=0;i<M.size();i++)
        {
            for(int j=i+1;j<M.size();j++)
            {
                if(M[i][j]==1)
                {
                    Union(i,j);
                }
            }
        }
        //一次遍历找到所有祖先节点,即为朋友圈的个数
        int res = 0;
        for(int i=0;i<M.size();i++)
        {
            if(i==father[i])
            {
                res++;
            }
        }
        return res;
    }
};

这是一个大佬的解法,也是模板,用的递归。

并查集主要有两个操作,一个是find(查),一个是union(并);

find的作用是找到一个元素所在集合的代表元素,主要是一个递归的过程;

union的作用就是将两个元素分别所在的集合合并成一个集合,就是将其中一个元素的代表元素变成另一个元素的代表元素

class Solution {
public:
    //查找祖先节点,当节点记录的祖先是自己,则表示查找到祖先了
    int find(vector<int> &Vec,int n){
        if(Vec[n]==-1)
            return n;
        return find(Vec,Vec[n]);
    }
	 //合并节点:设置共同祖先
    void Union(vector<int> &Vec,int m,int n){
        int parent_m=find(Vec,m);
        int parent_n=find(Vec,n);
        if(parent_m!=parent_n)
            Vec[parent_m]=parent_n;
    }

    int findCircleNum(vector<vector<int>>& M) {
        int N=M.size();
        vector<int> parent(N,-1);
        for(int i=0;i<N;++i){
            for(int j=0;j<N;++j){
                if(M[i][j]==1 && i!=j)
                    Union(parent,i,j);
            }
        }
        int count=0;
        for(int i=0;i<N;++i)
            if(parent[i]==-1)
                count++;
        return count;
    }
};

洛谷1536.村村通

题目描述

某市调查城镇交通状况,得到现有城镇道路统计表。表中列出了每条道路直接连通的城镇。市政府 “村村通工程” 的目标是使全市任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要相互之间可达即可)。请你计算出最少还需要建设多少条道路?

示例数据

输入包含若干组测试测试数据,每组测试数据的第一行给出两个用空格隔开的正整数,分别是城镇数目 n和道路数目 mm ;随后的 m 行对应 m条道路,每行给出一对用空格隔开的正整数,分别是该条道路直接相连的两个城镇的编号。简单起见,城镇从 1到 n编号。

注意:两个城市间可以有多条道路相通。

输入:

4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0

输出:

1
0
2
998

题解:

查并集嘛,和上面的一样呀

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e3+5;
int father[maxn];

void init()//初始化,最开始的每个人的代表都是自己
{
    for(int i=0;i<maxn;i++)
        father[i]=i;
}


int Find(int x)//查找
{
    int p,tmp;
    p=x;
    while(x!=father[x])
        x=father[x];
    while(p!=x)
    {
        tmp=father[p];
        father[p]=x;//进行路径压缩
        p=tmp;
    }
    return x;
}

void Merge(int x,int y)//合并
{
    int p=Find(x);
    int q=Find(y);
    if(p!=q)
        father[p]=q;
}


int main()
{
    int n,m,p;
    while(cin>>n>>m)
    {
        if(n==0)
            break;
        init();//初始化代表
        int x,y;
        for(int i=1;i<=m;i++)
        {
            cin>>x>>y;
            Merge(x,y);//合并
        }
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            if(i==father[i])
                ans++;
        }
        cout<<ans-1<<endl;
    }
    return 0;
}

动态规划

记忆化搜索和划分问题

53. 最大子序和

题目描述

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例数据

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

题解

对于每个元素,他只有进去和不进去两种状态,当算上后面的数字后,和变大了就放进去

dp[i]表示,nums中以nums[i]结尾的最大子序和

dp[0]=nums[0]

状态转移方程:

dp[i]=max(dp[i-1] + nums[i] , nums[i])

思考:如果让你得到这个子序列呢?

emmm我想的是,就最后再做一个循环,卡一下区间,找到递增的递增子序列和的前后索引

class Solution
{
public:
    int maxSubArray(vector<int> &nums)
    {
        //类似寻找最大最小值的题目,初始值一定要定义成理论上的最小最大值
        int result = INT_MIN;
        int numsSize = int(nums.size());
        //dp[i]表示nums中以nums[i]结尾的最大子序和
        vector<int> dp(numsSize);
        dp[0] = nums[0];
        result = dp[0];
        for (int i = 1; i < numsSize; i++)
        {
            dp[i] = max(dp[i - 1] + nums[i], nums[i]);
            result = max(result, dp[i]);
        }
        return result;
    }
};

300.最长上升子序列长度(不是连续的)

题目描述

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例数据

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。

你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

题解

dp[i]代表前面有几个比自己大的数字

状态转移方程:dp[i] = max(dp[i], dp[j] + 1)

初始化:dp[i] = 1,1 个字符显然是长度为 1的上升子序列。

做循环,进行dp,得到dp数组

在这里插入图片描述

思考:如何得到序列呢?

这个就有点麻烦了,所以就让我们一起跳过这个问题吧,耶

我觉得可以做一个vis数组进行同下标标记

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int len = nums.size();
        if (len < 2) {
            return len;
        }
		//赋初值
        vector<int> dp(len,1);

        for (int i = 1; i < len; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i]) {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
        }
		
        /*
        使用函数直接返回最大值
        return *max_element(dp.begin(), dp.end());
        */
        
        int res = 0;
        for (int i = 0; i < len; i++) {
            res = max(res, dp[i]);
        }
        return res;
    }
};

1143.最长公共子序列(LCS)

题目描述

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

示例数据

示例 1:

输入:text1 = "abcde", text2 = "ace" 
输出:3  
解释:最长公共子序列是 "ace",它的长度为 3。

示例 2:

输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc",它的长度为 3。

示例 3:

输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0。

提示:

  • 1 <= text1.length <= 1000
  • 1 <= text2.length <= 1000
  • 输入的字符串只含有小写英文字符。

题解

dp[i][j] 的含义是:对于 s1[1..i]s2[1..j],它们的 LCS 长度是 dp[i][j]

数据初始化:我们专门让索引为 0 的行和列表示空串,dp[0][..]dp[..][0] 都应该初始化为 0

状态转移:

对于 s1s2 中的每个字符,有什么选择?很简单,两种选择,要么在 lcs 中,要么不在。

状态转移方程:

在这里插入图片描述

①如果是第一行或者第一列就赋值为0

②如果两个字母相同的话就左上角加一

在这里插入图片描述

③如果两个字母不相同就上面 和左边找最大值

在这里插入图片描述

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int l1 = text1.length();
        int l2 = text2.length();
        int dp[1005][1005];
        for(int i=1;i<=l1;i++){
            for(int j=1;j<=l2;j++){
                if(text1.substr(i-1,1) == text2.substr(j-1,1)){
                    dp[i][j] = dp[i-1][j-1]+1;
                }else{
                    dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[l1][l2];
    }
};

516. 最长回文子序列

题目描述

给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000

示例数据

示例 1:

输入:
"bbbab"
输出:
4
一个可能的最长回文子序列为 "bbbb"。

示例 2:

输入:
"cbbd"
输出:
2
一个可能的最长回文子序列为 "bb"。

提示:

  • 1 <= s.length <= 1000
  • s 只包含小写英文字母

题解

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n = s.size();
        // dp 数组全部初始化为 0
        vector<vector<int>> dp(n, vector<int>(n, 0));
        // base case
        for (int i = 0; i < n; i++)
            dp[i][i] = 1;
        // 反着遍历保证正确的状态转移
        for (int i = n - 1; i >= 0; i--) {
            for (int j = i + 1; j < n; j++) {
                // 状态转移方程
                if (s[i] == s[j])
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                else
                    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
            }
        }
        // 整个 s 的最长回文子串长度
        return dp[0][n - 1];
    }
};

决策类动态规划如何解决

背包问题会单独出一篇文章,这里先占坑

01背包

完全背包

hdu1257.E - 最少拦截系统

http://acm.hdu.edu.cn/showproblem.php?pid=1257

题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超

过前一发的高度.某天,雷达捕捉到敌国的导弹来袭.由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹.

怎么办呢?多搞几套系统呗!你说说倒蛮容易,成本呢?成本是个大问题啊.所以俺就到这里来求救了,请帮助计算一下最少需要多少套拦截系统.

示例数据

输入:8 389 207 155 300 299 170 158 65
输出:2
解释:

题解

定义状态dp[i]表示从第一个数至第i个数的最长上升序列的元素个数

那么状态转移方程就是

if(num[i]>num[j])
	dp[i]=max(dp[i],dp[j]+1);

类似01背包,满足元素的大小关系后,有取和不取两种选择。

那么这样的话时间复杂度O(n^2)

#include<iostream>
#include<memory.h>
#include<cstdio>
#include<algorithm>
#include<cmath> 
using namespace std;
const int maxn=1e6+7;
int num[maxn];
int dp[maxn];
int main()
{
	int n;
	while(cin>>n){
	
	for(int i=1;i<=n;i++)
	{
		cin>>num[i];
	}
	dp[1]=1;
	for(int i=2;i<=n;i++)
	{
		dp[i]=1;
		for(int j=1;j<i;j++)
		{
			if(num[i]>num[j])
			{
				dp[i]=max(dp[i],dp[j]+1);
			}
		}
	}
	int maxx=dp[1];
	for(int i=2;i<=n;i++)
	{
		maxx=max(maxx,dp[i]);
	}
	cout<<maxx<<endl;
	}
	return 0;
} 

哈希表

1002. 查找常用字符

题目描述

给定仅有小写字母组成的字符串数组 A,返回列表中的每个字符串中都显示的全部字符(包括重复字符)组成的列表。例如,如果一个字符在每个字符串中出现 3 次,但不是 4 次,则需要在最终答案中包含该字符 3 次。

你可以按任意顺序返回答案。

示例数据

示例 1:

输入:["bella","label","roller"]
输出:["e","l","l"]

示例 2:

输入:["cool","lock","cook"]
输出:["c","o"

提示:

  1. 1 <= A.length <= 100
  2. 1 <= A[i].length <= 100
  3. A[i][j] 是小写字母

题解

169. 多数元素

题目描述

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例数据

示例 1:

输入: [3,2,3]
输出: 3

示例 2:

输入: [2,2,1,1,1,2,2]
输出: 2

题解

我们使用哈希映射(HashMap)来存储每个元素以及出现的次数。对于哈希映射中的每个键值对,键表示一个元素,值表示该元素出现的次数。

我们用一个循环遍历数组 nums 并将数组中的每个元素加入哈希映射中。

在这之后,我们遍历哈希映射中的所有键值对,返回值最大的键。

我们同样也可以在遍历数组 nums 时候使用打擂台的方法,维护最大的值,这样省去了最后对哈希映射的遍历。

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int, int> counts;
        int majority = 0, cnt = 0;
        for (int num: nums) {
            counts[num]+=1;
            if (counts[num] > cnt) {
                majority = num;
                cnt = counts[num];
            }
        }
        return majority;
    }
};
  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

笼中小夜莺

嘿嘿嘿,请用金钱尽情地蹂躏我吧

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

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

打赏作者

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

抵扣说明:

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

余额充值