leetcode每日一题系列

目录

23.6.27刷题之leetcode:98. 验证二叉搜索树

C++解法:

go解法:

23.6.28刷题之leetcode200. 岛屿数量

C++解法:

go解法:

23.6.29 刷题之leetcode130. 被围绕的区域

C++代码:

Go代码:

23.6.30刷题之leetcode133. 克隆图

C++解法:

Go解法:


23.6.27刷题之leetcode:98. 验证二叉搜索树

题目描述:

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

题目示例:

leetcode每日一题系列_第1张图片

 题目链接

https://leetcode.cn/problems/validate-binary-search-tree/?envType=study-plan-v2&envId=top-interview-150

题目分析可知,这是对二叉搜索树性质的一个考察。二叉搜索树在拥有二叉树性质的同时,还具有普通二叉树不具有的性质,即左节点 < 根节点 < 右节点。

由此我们可以想到用递归遍历的方式解决,每一次递归的时候,与当前节点进行比较,如果是递归左子树,就将本节点作为最大值;递归右子树,就将本节点作为最小值。

C++解法:

#include 

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
};

class Solution{
    public:
    //判断二叉树是否是有效的二叉搜索树
    bool isValidBST(TreeNode* root){
        //调用辅助函数进行递归判断,初始时最小值和最大值都为nullptr
        return isValidBSTHelper(root, nullptr, nullptr);
    }

    //辅助函数,用于递归判断二叉树是否是有效的二叉搜索树
    //参数node:当前节点
    //参数minNode:当前节点允许的最小值节点
    //参数maxNode:当前节点允许的最大值节点
    bool isValidBSTHelper(TreeNode* node, TreeNode* minNode, TreeNode* maxNode){
        //递归的终止条件,当节点为空时,说明遍历完了该子树,返回true
        if(node == nullptr){
            return true;
        }

        //检查当前节点的值是否在[minNode, maxNode]的范围内
        //如果不在范围内,则说明不满足二叉搜索树的条件,返回false
        if((minNode != nullptr && node->val <= minNode->val) || 
        (maxNode != nullptr && node->val >= maxNode->val)){
            return false;
        }

        //递归判断当前节点的左子树和右子树
        //对于左子树,当前节点成为最大值节点,对于右子树,当前节点成为最小值节点
        return isValidBSTHelper(node->left, minNode, node) && isValidBSTHelper(node->right, node, maxNode);
    }
}

go解法:

import "math"

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

//判断二叉树是否是有效的二叉搜索树
func isValidBST(root *TreeNode) bool {
	//调用辅助函数进行递归判断,初始时最小值和最大值为int类型的最小值和最大值
	return isValidBSTHelper(root, math.MinInt64, math.MaxInt64)
}

//辅助函数,用于递归判断二叉树是否是有效的二叉搜索树
//参数node:当前节点
//参数minVal:当前节点允许的最小值
//参数maxVal:当前节点允许的最大值
func isValidBSTHelper(node *TreeNode, minVal, maxVal int) bool {
	//递归的终止条件,当节点为空时,说明遍历完了该子树,返回true
	if node == nil {
		return true
	}
	//检查当前节点的值是否在[minVal,maxVal]的范围内
	//如果不在范围内,则说明不满足二叉搜索树的条件,返回false
	if node.Val <= minVal || node.Val >= maxVal {
		return false
	}

	//递归判断当前节点的左子树和右子树
	//对于左子树,更新最大值为当前节点的值,对于右子树,更新最小值为当前节点的值
	return isValidBSTHelper(node.Left, minVal, node.Val) && isValidBSTHelper(node.Right, node.Val, maxVal)
}

23.6.28刷题之leetcode200. 岛屿数量

题目描述:

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

题目示例:

leetcode每日一题系列_第2张图片

 题目链接:

https://leetcode.cn/problems/number-of-islands/?envType=study-plan-v2&envId=top-interview-150

题目分析可知,考察的是dfs深度遍历,但是有需要我们获取岛屿的确切数量。由于dfs会重复遍历同一个节点,所以我们就需要在dfs的时候,将以访问的节点标记。

C++解法:

#include 

class Solution{
    public:
    int numIslands(vector>& grid){
        if (grid.empty()){
            return 0;
        }

        int m = grid.size();   //获取行数
        int n = grid[0].size(); //获取列数
        int count = 0;  

        //遍历二维网格的每个位置
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if (grid[i][j] == '1'){
                    //当遇到陆地时,递归将当前陆地相连的所有陆地置为已经访问,并增加岛屿数量计数
                    dfs(grid, i, j);
                    count++;
                }
            }
       }
    return count;
    }

    private:
    void dfs(vector>& grid, int row, int col){
        int m = grid.size();
        int n = grid[0].size();

        //边界条件判断,如果越界或当前位置不是陆地,则返回
        if(row < 0 || col < 0 || row >= m || col >= n || grid[row][col] != '1'){
            return;
        }

        //将当前陆地置为已经访问
        grid[row][col] = '2';

        //递归处理相邻的陆地
        dfs(grid, row - 1, col); //上
        dfs(grid, row + 1, col); //下
        dfs(grid, row, col - 1); //左
        dfs(grid, row, col + 1); //右
    }
};

go解法:

func numIslands(grid [][]byte) int {
	if len(grid) == 0 {
		return 0
	}

	m := len(grid)
	n := len(grid[0])
	count := 0

	//遍历二维网格的每个位置
	for i := 0; i < m; i++ {
		for j := 0; j < n; j++ {
			if grid[i][j] == '1' {
				//当遇到陆地时,递归将与当前陆地相邻的所有节点,标记为已访问,并增加岛屿数量计数
				dfs(grid, i, j)
				count++
			}
		}
	}
	return count
}

func dfs(grid [][]byte, i, j int) {
	m := len(grid)
	n := len(grid[0])

	// 边界条件判断,如果越界或当前位置不是陆地,则返回
	if i < 0 || j < 0 || i >= m || j >= n || grid[i][j] == '0' {
		return
	}

	// 将当前陆地置为已访问
	grid[i][j] = '0'

	// 递归处理相邻的陆地
	dfs(grid, i-1, j) // 上
	dfs(grid, i+1, j) // 下
	dfs(grid, i, j-1) // 左
	dfs(grid, i, j+1) // 右
}

23.6.29 刷题之leetcode130. 被围绕的区域

题目描述:

给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。

题目示例:

leetcode每日一题系列_第3张图片

 题目链接:

https://leetcode.cn/problems/surrounded-regions/description/

题目分析可知,这是考察图的一道算法题。题目要求我们找到所有被'x'包围的'0',并且要让我们使得其变成'x'。咋一看之下没有突破口,但是题目也给我提供了一条信息,那就是边界的'0'是不会被'x'包围的。那么与其相邻的'0'也就是不可能被'x'包围的是不是?由此,我们有了一个突破口,我们可以首先遍历边界的'0',并在遍历的时候,执行深度遍历,以避免多次访问。我们在遍历的时候将'0'标记为'#'。最后在遍历整个矩阵,令剩下的所有'0',即所有被'x'包围的'0'置为'x'。让所有的'#'恢复为'0'。

C++代码:

#include 

class Solution{
    public:
    void solve(vector>& board){
        if (board.empty()){
            return;
        }

        int m = board.size();  //获取行数
        int n = board[0].size();  //获取列数

        //标记边界上的'0'及其相连的'0',将其设为特殊字符'#'
        for(int i = 0; i < m; i++){
            if(board[i][0] == 'O'){
                dfs(board, i, 0);
            }
            if(board[i][n - 1] == 'O'){
                dfs(board, i, n - 1);
            }
        }
        for(int j = 0; j < n; j++){
            if(board[0][j] == 'O'){
                dfs(board, 0, j);
            }
            if(board[m - 1][j] == 'O'){
                dfs(board, m - 1, j);
            }
        }

        //遍历整个矩阵,将剩余的'0'修改为'X',将特殊字符'#'恢复为'0'
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(board[i][j] == 'O'){
                    board[i][j] = 'X';
                }
                if(board[i][j] == '#'){
                    board[i][j] = 'O';
                }
            }
        }
        void dfs(vector>& board, int i, int j){
            int m = board.size();
            int n = board[0].size();

            if(i < 0 || i >= m || j < 0 || j >= n || board[i][j] != 'O'){
                return;
            }
            board[i][j] = '#'; //将当前位置设为特殊字符'#'

            //递归处理相邻的位置
            dfs(board, i - 1, j); //上
            dfs(board, i + 1, j); //下
            dfs(board, i, j - 1); //左
            dfs(board, i, j + 1); //右
        }
    }
};

Go代码:

func solve(board [][]byte) {
	if len(board) == 0 {
		return
	}

	m := len(board)
	n := len(board[0])

	//标记边界上的'0'及其相连的'0',将其设为特殊字符'#'
	for i := 0; i < m; i++ {
		if board[i][0] == 'O' {
			dfs(board, i, 0)
		}
		if board[i][n-1] == 'O' {
			dfs(board, i, n-1)
		}
	}
	for j := 0; j < n; j++ {
		if board[0][j] == 'O' {
			dfs(board, 0, j)
		}
		if board[m-1][j] == 'O' {
			dfs(board, m-1, j)
		}
	}

	//遍历整个矩阵,将剩余的'0'修改为'X',将特殊字符'#'恢复为'0'
	for i := 0; i < m; i++ {
		for j := 0; j < n; j++ {
			if board[i][j] == 'O' {
				board[i][j] = 'X'
			}
			if board[i][j] == '#' {
				board[i][j] = 'O'
			}
		}
	}
}

func dfs(board [][]byte, row, col int) {
	m := len(board)
	n := len(board[0])

	if row < 0 || row >= m || col < 0 || col >= n || board[row][col] != 'O' {
		return
	}

	board[row][col] = '#' // 将当前位置设为特殊字符 '#'

	// 递归处理相邻的位置
	dfs(board, row-1, col) // 上
	dfs(board, row+1, col) // 下
	dfs(board, row, col-1) // 左
	dfs(board, row, col+1) // 右
}

23.6.30刷题之leetcode133. 克隆图

题目描述:

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。

图中的每个节点都包含它的值 valint) 和其邻居的列表(list[Node])。

class Node {
    public int val;
    public List neighbors;
}

测试用例格式:

简单起见,每个节点的值都和它的索引相同。例如,第一个节点值为 1(val = 1),第二个节点值为 2(val = 2),以此类推。该图在测试用例中使用邻接列表表示。

邻接列表 是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。

给定节点将始终是图中的第一个节点(值为 1)。你必须将 给定节点的拷贝 作为对克隆图的引用返回。

题目示例:

leetcode每日一题系列_第4张图片

 题目链接:

https://leetcode.cn/problems/clone-graph/?envType=study-plan-v2&envId=top-interview-150

题目分析可知这是关于图这个数据结构的算法题。题目要求我们在有向无环图中的任意一个节点开始,获得这个图的拷贝。由于题目中给出了节点的结构,除了其本身的值以外,还携带有邻居节点的线性表。由此我们可以深度遍历其邻居,在遍历的过程中复制节点,进而可以获得整张图的拷贝。当然需要注意的就是,在深度遍历的过程中,需要避免多次访问的情况。

C++解法:

#include 
#include 

class Node{
    public:
    int Val;
    std::vector neighbors;

    Node(int _val){
        val = _val;
    }
};

class Solution{
    public:
    Node* cloneGraph(Node* node){
        if(node == NULL){
            return nullptr;
        }

        //用于存储已访问过的节点和它们的拷贝
        std::unordered_map visited;

        //创建给点节点的拷贝
        Node* cloneNode = new Node(node->Val);
        visited[node] = cloneNode;

        //深度优先搜索克隆图
        cloneGraphHelper(node, visited);

        return cloneNode;
    }

    private:
    void cloneGraphHelper(Node* node, std::unordered_map& visited){
        //遍历节点的邻居
        for(Node* neighbor : node->neighbors){
            if(visited.find(neighbor) == visited.end()){
                //如果邻居节点还未被访问过,则创建它的拷贝并进行递归拷贝
                Node* cloneNeighbor = new Node(neighbor->Val);
                visited[neighbor] = cloneNeighbor;
                visited[node]->neighbors.push_back(cloneNeighbor);
                cloneGraphHelper(neighbor, visited);
            }else{
                //如果邻居节点已经被访问过,则直接使用已有的拷贝
                visited[node]->neighbors.push_back(visited[neighbor]);
            }
        }
    }
};

Go解法:

type Node struct {
	Val       int     //节点的值
	Neighbors []*Node //节点的邻居列表
}

//cloneGraph 克隆给定节点的图
func cloneGraph(node *Node) *Node {
	if node == nil {
		return nil
	}

	//存储已访问过的节点和对应的拷贝节点
	visited := make(map[*Node]*Node)

	//创建给点节点的拷贝节点
	cloneNode := &Node{Val: node.Val}
	visited[node] = cloneNode

	cloneGraphHelper(node, visited) //深度优先搜索克隆图

	//返回给点节点的拷贝节点作为克隆图的引用
	return cloneNode
}

//cloneGraphHelper 是递归函数,用于深度优先搜索克隆图
func cloneGraphHelper(node *Node, visited map[*Node]*Node) {
	//遍历节点的邻居列表
	for _, neighbor := range node.Neighbors {
		if _, ok := visited[neighbor]; !ok {
			//如果邻居节点还没被访问过,则创建它的拷贝节点并进行递归拷贝
			cloneNeighbor := &Node{Val: neighbor.Val}                                //创建邻居节点的拷贝节点
			visited[neighbor] = cloneNeighbor                                        //将邻居节点和它的拷行节点放入visited中
			visited[node].Neighbors = append(visited[node].Neighbors, cloneNeighbor) // 将拷贝节点加入当前节点的拷贝节点的邻居列表中
			cloneGraph(neighbor, visited)                                            //递归拷贝邻居节点的图
		} else {
			//如果邻居节点已经被访问过,则直接使用已有的拷贝节点
			visited[node].Neighbors = append(visited[node].Neighbors, visited[neighbor])
		}
	}
}

23.7.1刷题

1.牛客JZ64 求1+2+3+...+n

题目描述:

leetcode每日一题系列_第5张图片

题目链接:

https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

C++解法:

可以通过简单的递归解决。

class Solution {
public:
    int Sum_Solution(int n) {
        //通过与运算判断n是否为正数,以结束递归。
        n && (n += Sum_Solution(n - 1));
        return n;
    }
};

Go解法:

在Go语言中,无法直接使用递归实现条件累加,因为Go语言不支持与运算符的短路特性。但是可以通过使用循环来实现类似的功能。

package main

import "fmt"

func SumSolution(n int) int {
	sum := 0
	for n > 0 {
		sum += n
		n--
	}
	return sum
}

2.HJ73 计算日期到天数转换

题目描述:

leetcode每日一题系列_第6张图片

题目链接:

https://www.nowcoder.com/practice/769d45d455fe40b385ba32f97e7bcded?tpId=37&&tqId=21296&rp=1&ru=/activity/oj&qru=/ta/huawei/question-ranking

其实这道题目主要注意的就是闰年的计算方法,因为闰年的二月份比其他年份多一天。

年份是四的倍数是闰年,一百的倍数不是闰年,四百的倍数又是闰年。 

 C++解法:

#include 

using namespace std;

int main()
{
    int year, month, day;
    int data[] = {31,28,31,30,31,30,31,31,30,31,30,31};

    while(cin>>year>>month>>day)
    {
        int sum;
        for(int i = 0; i < month-1; i++)
        {
            sum = sum + data[i];
        }
        if((year%400 == 0|| (year%4 == 0 && year%100 != 0)) && month > 2)
        {
            sum = sum + 1 + day;
        }
        else
        {
            sum = sum + day;
        }
        cout << sum << endl;
    }
    return 0;
}

Go解法:

package main

import "fmt"

func getDayOfYear(year, month, day int) int {
	daysOfMonth := []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
	sum := 0

	for i := 1; i < month; i++ {
		sum += daysOfMonth[i-1]
	}

	if (year%4 == 0 && year%100 != 0) || (year%400 == 0) && month > 2 {
		sum += 1
	}

	sum += day

	return sum
}

func main() {
	var year, month, day int
	fmt.Scan(&year, &month, &day)

	dayOfYear := getDayOfYear(year, month, day)
	fmt.Println(dayOfYear)
}

3.牛客KY111 日期差值

题目描述:

leetcode每日一题系列_第7张图片

 题目链接:

https://www.nowcoder.com/practice/ccb7383c76fc48d2bbc27a2a6319631c?tpId=62&&tqId=29468&rp=1&ru=/activity/oj&qru=/ta/sju-kaoyan/question-ranking

#include 
#include 

using namespace std;

int DayTab[2][13] = {
    {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},  // 非闰年的每个月的天数
    {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}   // 闰年的每个月的天数
};

// 判断是否为闰年
bool IsLeapYear(int year) {
    return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}

int YearTab[2] = {365, 366};  // 非闰年和闰年的天数

int main() {
    int year1, month1, day1;
    int year2, month2, day2, minyear, maxyear;
    int number, num1, num2;

    // 循环读取输入,直到遇到文件结束符
    while (scanf("%04d%02d%02d\n%04d%02d%02d", &year1, &month1, &day1, &year2, &month2, &day2) != EOF) {
        number = 0;
        minyear = year1 < year2 ? year1 : year2;    // 较小的年份
        maxyear = (minyear == year1) ? year2 : year1;   // 较大的年份

        // 计算两个年份之间的天数
        while (minyear < maxyear) {
            number += YearTab[IsLeapYear(minyear)];
            ++minyear;
        }

        num1 = 0;
        for (int i = 1; i < month1; ++i) {
            num1 += DayTab[IsLeapYear(year1)][i];   // 累加起始年份的月份天数
        }
        num1 += day1;   // 加上起始年份的日数

        num2 = 0;
        for (int i = 1; i < month2; ++i) {
            num2 += DayTab[IsLeapYear(year2)][i];   // 累加结束年份的月份天数
        }
        num2 += day2;   // 加上结束年份的日数

        number += year1 < year2 ? (num2 - num1 + 1) : (num1 - num2 + 1);   // 计算总天数,考虑起始年份和结束年份的大小关系
        cout << number << endl;
    }

    return 0;
}

7.2刷题之leetcode207. 课程表

题目描述:

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

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程  bi 。

  • 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。

请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

题目示例:

leetcode每日一题系列_第8张图片

题目链接:

 https://leetcode.cn/problems/course-schedule/

分析题目可知,可以通过邻接表去解答这道题目。因为课程的先后顺序刚好可以通过邻接表来表示,同时需要表明每门课程的先修课程有多少,以保证我们在知道学完了多少先修课程可以学习这门课程。

C++解法:

#include 
#include 
#include 

using namaspace std;

bool canFinish(int numCourses, vector>& prerequisites) {
    //构建图的邻接表表示
    vector> graph(numCourses);  //存储课程的先修课程关系
    vector inDegree(numCourses, 0);  //存储每门课程的入度

    //构建图
    for(auto& prerequisite : prerequisites) {
        int course = prerequisite[0];  //当前课程
        int prerequisite = prerequisite[1];  //先修课程
        graph[prerequisiteCourse].push_back(course); //在先修课程的邻接表中添加当前课程
        inDegree[course]++;  //当前课程的入度加1
    }

    //初始化一个队列,用于存储入度为0的节点
    queue q;
    for(int i = 0; i < numCourses; i++) {
        if(inDegree[i] == 0) {
            q.push(i);
        }
    }

    //拓扑排序
    while(!q.empty()) {
        int curr = q.front();
        q.pop();
        numCourses--;

        //遍历当前节点的所有后继节点
        for(int next : graph[curr]) {
            inDree[next]--;
            if(inDree[next] == 0) {
                q.push(next);
            }
        }
    }

    //如果所有课程都完成了学习,则返回true
    return numCourses == 0;
}
  1. 创建一个图的邻接表表示(graph),用于存储课程的先修课程关系。
  2. 创建一个入度数组(indegree),用于记录每门课程的入度。
  3. 根据输入的先修课程关系,构建图的邻接表和更新入度数组。
  4. 初始化一个队列(q),用于存储入度为 0 的节点。
  5. 将入度为 0 的节点加入队列。即可以直接学习的课程。
  6. 进行拓扑排序,直到队列为空:
    • 从队列中取出一个节点(curr)。
    • 将该节点的后继节点的入度减一。因为当前课程的先修课程已经学完,其必须要学的先修课程数量现在可以减去1。
    • 如果减一后的入度为 0,将该节点加入队列。
    • 计数器 numCourses 减一,表示已经学习了一门课程。
  7. 判断是否所有课程都已经学习,即 numCourses 是否为 0。
  8. 返回结果。

Go解法:

package main

import(
	"fmt"
)

func canFinish(numCourses int, prerequisites [][]int) bool {
	//构建图的邻接表表示
	graph := make([][]int, numCourses)  //存储课程的先修课程关系
	indegree := make([]int, numCourses) //存储每门课程的入度


	//构建图
	for _, prerequisite := range prerequisites {
		course := prerequisite[0]   //当前课程
		prerequisiteCourse := prerequisite[1] //当前课程的先修课程
		graph[prerequisiteCourse] = append(graph[prerequisiteCourse], course)
		indegree[course]++    //当前课程的入度加1
	}

	//初始化一个队列,用于存储入度为0的节点
	queue := []int{}
	for i := 0; i < numCourses; i++ {
		if indegree[i] == 0 {
			queue = append(queue, i)
		}
	}

	//拓扑排序
	for len(queue) > 0 {
		curr := queue[0]
		queue = queue[1:]
		numCourses--


		//遍历当前节点的所有后继节点
		for _, next := range graph[curr] {
			indegree[next]--
			if indegree[next] == 0 {
				queue = append(queue, next)
			}
		}
	}
	
	//如果所有课程都完成了学习,则返回true
	return numCourses == 0
}

其思路同C++版本一致,只是语言不同导致的代码有些不同。

在Go语言中,我们使用切片(Slice)来代替C++中的vector,使用数组(Array)来代替C++中的数组。逻辑和算法与C++版本保持一致,通过构建邻接表和拓扑排序来判断是否能完成所有课程的学习。输出结果与C++版本相同。

你可能感兴趣的:(leetcode,算法,c++,开发语言,数据结构)