152乘积最大子数组;200岛屿数量;并查集;207课程表;拓扑排序

本文探讨了两个经典算法问题:寻找数组中乘积最大的连续子数组及计算二维网格中岛屿的数量。文章提供了详细的解决方案,包括动态规划、深度优先搜索和并查集等算法的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字)。

 

示例 1:

输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。


示例 2:

输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

class Solution {//该方法非常笨,虽然一开始我也觉得应该用动态规划,但死活没有思路,因为有个负数可能会出现最大值变成最小值的情况
/*整体思路:
1以0为分界点分为几个小块,取几个小块的乘最大值,(0)...A...0...B...(0)
2第一种小块:单独一个0,
3第二种小块:块内负数个数为偶数个,则返回范围全部乘积
4第三种小块:块内负数个数为奇数个,则返回max(从左乘到最后一个负数前,从右乘到最后一个负数前)
*/
public:
    long long resMax=INT_MIN;    
    int maxProduct(vector<int>& nums) {
        if(nums.size()==0)return 0;
        if(nums.size()==1)return nums[0];
        bool hasZero=false;
        int minusCount=0,le=0;
        for(int i=0;i<nums.size();++i){
            if(nums[i]<0)++minusCount;
            if(nums[i]==0){
                hasZero=true;
                helper(nums,le,i-1,minusCount);
                le=i+1;
                minusCount=0;
            }
        }
        helper(nums,le,nums.size()-1,minusCount);
        return resMax<0&&hasZero?0:resMax;//resMax存放第二第三种情况,hasZero判断比较第一种情况  
    }
    void helper(vector<int> &nums,int le,int ri,int c){
        if(ri<0||le>ri)return;
        if(le==ri)
            resMax=max(resMax,(long long)nums[le]);
        long long pathMax=1;
        if(c%2==0){//第二种情况
            for(int i=le;i<=ri;++i)
                pathMax*=nums[i];
            resMax=max(resMax,pathMax);
        }
        else{//第三种情况
            for(int i=le,j=0;;++i){
                if(nums[i]<0)++j;
                if(j==c)break;
                pathMax*=nums[i];
                resMax=max(resMax,pathMax);
            }
            pathMax=1;
            for(int i=ri,j=0;;--i){
                if(nums[i]<0)++j;
                if(j==c)break;
                pathMax*=nums[i];
                resMax=max(resMax,pathMax);
            }            
        }
    }
};
//动态规划:
dpmax=max(dpmax*nums[i],nums[i]);
dpmin=max(dpmin*nums[i],nums[i]);

给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。

示例 1:

输入:
11110
11010
11000
00000

输出: 1


示例 2:

输入:
11000
11000
00100
00011

输出: 3

class Solution {//直觉 深度优先遍历
public:
    int resCount=0;
    vector<vector<char>> grid;
    int numIslands(vector<vector<char>>& grid) {
        if(grid.size()==0)return 0;
        this->grid=grid;
        vector<vector<bool>> covered(grid.size(),vector<bool>(grid[0].size(),false));//可以用grid[i][j]=‘0’;的方法表示已遍历过
        for(int i=0;i<grid.size();++i)
            for(int j=0;j<grid[0].size();++j)
                if(!covered[i][j]&&grid[i][j]=='1'){
                    helper(covered,i,j);
                    ++resCount;
                }
        return resCount;            
            
    }
    void helper(vector<vector<bool>> &covered,int i,int j){
        if(i<0||i>=grid.size()||j<0||j>=grid[0].size()||covered[i][j]||grid[i][j]=='0')return;        
        covered[i][j]=true;
        helper(covered,i+1,j);
        helper(covered,i-1,j);
        helper(covered,i,j+1);
        helper(covered,i,j-1);
    }
};
//方法二:广度优先遍历,队列

什么是并查集(Disjoint-set)

对于一个集合S={a1, a2, ..., an-1, an},我们还可以对集合S进一步划分: S1,S2,...,Sm-1,Sm,我们希望能够快速确定S中的两两元素是否属于S的同一子集。
举个栗子,S={0,1, 2, 3, 4, 5, 6},如果我们按照一定的规则对集合S进行划分,假设划分后为S1={1, 2, 4}, S2={3, 6},S3={0, 5},任意给定两个元素,我们如何确定它们是否属于同一子集?某些合并子集后,又如何确定两两关系?基于此类问题便出现了并查集这种数据结构。
并查集有两个基本操作:

  • Find: 查找元素所属子集
  • Union:合并两个子集为一个新的集合

并查集(初始化+find+union)

 

class UnionFind {
//private:
  vector<int> parent;//存放 目标数组 对应元素所属集合的下标/父节点下标
  vector<int> rank;//秩/数高度 合并优化措施
  int count; //连通个数
  //根据需要设置其他参数
public:
  UnionFind(vector<vector<char>>& grid) {//初始化 目标/操作数组 一维或者二维
    count = 0;
    int m = grid.size();
    int n = grid[0].size();
      for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
          //初始化start
          //一般一维数组:parend.push_back(i),++count,rand.push_back(0);
          //一般二维数组:parend.push_back(i * n + j),++count,rand.push_back(0);二维映射到一维i*n+j
          //一般情况下,传入参数为对应数组的总元素个数即可
          if (grid[i][j] == '1') {//目标数组中实际要合并的可能元素,一般是全体元素无需判断
            parent.push_back(i * n + j);
            ++count;
          }
          else parent.push_back(-1);//
          rank.push_back(0);
          //初始化end
        }
    }
  }

  int find(int x) { //路径压缩优化,找到则直接指向最终下标/父节点下标
    return x == parent[x] ? x : (parent[x] = find(parent[x]));
  }

  void Union(int x, int y) { // 按秩合并优化
    int rootx = find(x);
    int rooty = find(y);
    if (rootx != rooty) {
      if (rank[rootx] > rank[rooty]) 
          parent[rooty] = rootx;
      else if (rank[rootx] < rank[rooty]) 
          parent[rootx] = rooty;
      else {
          parent[rooty] = rootx; //parent[rootx] = rooty,++rank[rooty];
          ++rank[rootx];
      }
      --count;
    }
  }

  int getCount() const {
    return count;
  }

};
//官方并查集答案
class Solution {
public:
  int numIslands(vector<vector<char>>& grid) {
    int nr = grid.size();
    if (!nr) return 0;
    int nc = grid[0].size();
    UnionFind uf (grid);//初始化
    int num_islands = 0;
    for (int r = 0; r < nr; ++r) {
      for (int c = 0; c < nc; ++c) {
        if (grid[r][c] == '1') {
          grid[r][c] = '0';
          //满足条件则合并
          if (r - 1 >= 0 && grid[r-1][c] == '1') uf.Union(r * nc + c, (r-1) * nc + c);
          if (r + 1 < nr && grid[r+1][c] == '1') uf.Union(r * nc + c, (r+1) * nc + c);
          if (c - 1 >= 0 && grid[r][c-1] == '1') uf.Union(r * nc + c, r * nc + c - 1);
          if (c + 1 < nc && grid[r][c+1] == '1') uf.Union(r * nc + c, r * nc + c + 1);
        }
      }
    }

    return uf.getCount();
  }
};

你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]

给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?

 

示例 1:

输入: 2, [[1,0]] 
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。

示例 2:

输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

 

提示:


    输入的先决条件是由 边缘列表 表示的图形,而不是 邻接矩阵 。详情请参见图的表示法。
    你可以假定输入的先决条件中没有重复的边。
    1 <= numCourses <= 10^5

class Solution {
public:
    vector<vector<int>> v;
    unordered_map<int,vector<int>> m;
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        if(prerequisites.size()==0||numCourses<=1)return true;
        vector<int> flag(numCourses,1);//0表示有先决课程,1表示没有先决课程,2表示这个课程是当前正在dfs判断的课程
        this->v=prerequisites;
        for(auto i:v){
            m[i[0]].push_back(i[1]);
            flag[i[0]]=0;
        }
        for(auto i:m)
            if(!helper(i.first,flag))return false;
        return true;
    }
    bool helper(int mi,vector<int> &flag){
        if(flag[mi]==1)return true;
        if(flag[mi]==2)return false;
        flag[mi]=2;
        for(auto i:m[mi])
            if(!helper(i,flag))
                return false;
        flag[mi]=1;
        return true;
    }
};

拓扑排序(一维存入度,二维存出度,队列遍历0入度节点)

在一个有向图中,对所有的节点进行排序,要求没有一个节点指向它前面的节点。

先统计所有节点的入度,对于入度为0的节点就可以分离出来,然后把这个节点指向的节点的入度减一。

一直做改操作,直到所有的节点都被分离出来。

如果最后不存在入度为0的节点,那就说明有环,不存在拓扑排序,也就是很多题目的无解的情况。
 

class Solution {
public:
   bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
	vector<int> indegree(numCourses,0);//入度
	vector<vector<int>> graph(numCourses);//存的是出边
	for (int i = 0; i < prerequisites.size(); i++)
	{
		indegree[prerequisites[i][0]]++;
		graph[prerequisites[i][1]].push_back(prerequisites[i][0]);
	}

	//将入度为0的顶点入队
	queue<int> myqueue;
	for (int i = 0; i < numCourses; i++)
		if (indegree[i] == 0)
			myqueue.push(i);
	int cnt = 0;//vector<int> res;拓扑排序顺序存储
	while (!myqueue.empty())
	{
		int temp = myqueue.front();
		myqueue.pop();
		cnt++;//res.push_back(temp);
		for (int i = 0; i < graph[temp].size(); i++)
		{
			indegree[graph[temp][i]]--;
			if (indegree[graph[temp][i]] == 0)
				myqueue.push(graph[temp][i]);
		}		
	}
	return cnt == numCourses;//return res.size()==numCourses
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值