目录
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解法:
题目描述:
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
题目示例:
题目链接
https://leetcode.cn/problems/validate-binary-search-tree/?envType=study-plan-v2&envId=top-interview-150
题目分析可知,这是对二叉搜索树性质的一个考察。二叉搜索树在拥有二叉树性质的同时,还具有普通二叉树不具有的性质,即左节点 < 根节点 < 右节点。
由此我们可以想到用递归遍历的方式解决,每一次递归的时候,与当前节点进行比较,如果是递归左子树,就将本节点作为最大值;递归右子树,就将本节点作为最小值。
#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);
}
}
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)
}
题目描述:
给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
题目示例:
题目链接:
https://leetcode.cn/problems/number-of-islands/?envType=study-plan-v2&envId=top-interview-150
题目分析可知,考察的是dfs深度遍历,但是有需要我们获取岛屿的确切数量。由于dfs会重复遍历同一个节点,所以我们就需要在dfs的时候,将以访问的节点标记。
#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); //右
}
};
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) // 右
}
题目描述:
给你一个 m x n
的矩阵 board
,由若干字符 'X'
和 'O'
,找到所有被 'X'
围绕的区域,并将这些区域里所有的 'O'
用 'X'
填充。
题目示例:
题目链接:
https://leetcode.cn/problems/surrounded-regions/description/
题目分析可知,这是考察图的一道算法题。题目要求我们找到所有被'x'包围的'0',并且要让我们使得其变成'x'。咋一看之下没有突破口,但是题目也给我提供了一条信息,那就是边界的'0'是不会被'x'包围的。那么与其相邻的'0'也就是不可能被'x'包围的是不是?由此,我们有了一个突破口,我们可以首先遍历边界的'0',并在遍历的时候,执行深度遍历,以避免多次访问。我们在遍历的时候将'0'标记为'#'。最后在遍历整个矩阵,令剩下的所有'0',即所有被'x'包围的'0'置为'x'。让所有的'#'恢复为'0'。
#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); //右
}
}
};
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) // 右
}
题目描述:
给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。
图中的每个节点都包含它的值 val
(int
) 和其邻居的列表(list[Node]
)。
class Node { public int val; public Listneighbors; }
测试用例格式:
简单起见,每个节点的值都和它的索引相同。例如,第一个节点值为 1(val = 1
),第二个节点值为 2(val = 2
),以此类推。该图在测试用例中使用邻接列表表示。
邻接列表 是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。
给定节点将始终是图中的第一个节点(值为 1)。你必须将 给定节点的拷贝 作为对克隆图的引用返回。
题目示例:
题目链接:
https://leetcode.cn/problems/clone-graph/?envType=study-plan-v2&envId=top-interview-150
题目分析可知这是关于图这个数据结构的算法题。题目要求我们在有向无环图中的任意一个节点开始,获得这个图的拷贝。由于题目中给出了节点的结构,除了其本身的值以外,还携带有邻居节点的线性表。由此我们可以深度遍历其邻居,在遍历的过程中复制节点,进而可以获得整张图的拷贝。当然需要注意的就是,在深度遍历的过程中,需要避免多次访问的情况。
#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]);
}
}
}
};
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])
}
}
}
题目描述:
题目链接:
https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
可以通过简单的递归解决。
class Solution {
public:
int Sum_Solution(int n) {
//通过与运算判断n是否为正数,以结束递归。
n && (n += Sum_Solution(n - 1));
return n;
}
};
在Go语言中,无法直接使用递归实现条件累加,因为Go语言不支持与运算符的短路特性。但是可以通过使用循环来实现类似的功能。
package main
import "fmt"
func SumSolution(n int) int {
sum := 0
for n > 0 {
sum += n
n--
}
return sum
}
题目描述:
题目链接:
https://www.nowcoder.com/practice/769d45d455fe40b385ba32f97e7bcded?tpId=37&&tqId=21296&rp=1&ru=/activity/oj&qru=/ta/huawei/question-ranking
其实这道题目主要注意的就是闰年的计算方法,因为闰年的二月份比其他年份多一天。
年份是四的倍数是闰年,一百的倍数不是闰年,四百的倍数又是闰年。
#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;
}
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)
}
题目描述:
题目链接:
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;
}
题目描述:
你这个学期必须选修 numCourses
门课程,记为 0
到 numCourses - 1
。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites
给出,其中 prerequisites[i] = [ai, bi]
,表示如果要学习课程 ai
则 必须 先学习课程 bi
。
[0, 1]
表示:想要学习课程 0
,你需要先完成课程 1
。请你判断是否可能完成所有课程的学习?如果可以,返回 true
;否则,返回 false
。
题目示例:
题目链接:
https://leetcode.cn/problems/course-schedule/
分析题目可知,可以通过邻接表去解答这道题目。因为课程的先后顺序刚好可以通过邻接表来表示,同时需要表明每门课程的先修课程有多少,以保证我们在知道学完了多少先修课程可以学习这门课程。
#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;
}
graph
),用于存储课程的先修课程关系。indegree
),用于记录每门课程的入度。q
),用于存储入度为 0 的节点。curr
)。numCourses
减一,表示已经学习了一门课程。numCourses
是否为 0。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++版本相同。