hot100(8)

71.10. 正则表达式匹配 - 力扣(LeetCode)

动态规划

题解:10. 正则表达式匹配题解 - 力扣(LeetCode)

72.5. 最长回文子串 - 力扣(LeetCode)

动态规划

1.dp数组及下标含义

dp[i][j] : 下标i到下标j的子串是否是回文串

2.递推方程

dp[i][j] = dp[i+1][j-1] && s.charAt(i) == s.charAt(j)

3.初始化

对于单个字母,dp[i][i] = true;

对于两个字母的子串,如果两个字母相同,dp[i][i+1] = true;

4.递推顺序

5.dp数组举例说明

public String longestPalindrome(String s) {if(s.length() < 2){return s;}boolean[][] dp = new boolean[s.length()][s.length()];for(int i = 0 ; i < s.length() ; i++){dp[i][i] = true;}int maxlen = 0,start = 0;for(int len = 2 ; len <= s.length() ; len++){for(int i = 0 ; i < s.length() - len + 1 ; i++){int j = i + len - 1;if(s.charAt(i) != s.charAt(j)){dp[i][j] = false;}else{if(j - i < 3){dp[i][j] = true;}else{dp[i][j] = dp[i+1][j-1];}}if(dp[i][j] && j - i + 1 > maxlen){maxlen = j - i + 1;start = i;}}}return s.substring(start,start +maxlen);}

方法二类似于使用滚动数组优化动态规划的空间复杂度的本质,只使用上一个状态,不必保留所有状态。

枚举出所有的中心,即可得到所有可能的回文子串(必由某一中心扩展而来)

class Solution {public String longestPalindrome(String s) {if (s == null || s.length() < 1) {return "";}int start = 0, end = 0;for (int i = 0; i < s.length(); i++) {int len1 = expandAroundCenter(s, i, i);int len2 = expandAroundCenter(s, i, i + 1);int len = Math.max(len1, len2);if (len > end - start) {start = i - (len - 1) / 2;end = i + len / 2;}}return s.substring(start, end + 1);}public int expandAroundCenter(String s, int left, int right) {while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {--left;++right;}return right - left - 1;}
}

73.

74.3. 无重复字符的最长子串 - 力扣(LeetCode)

滑动窗口

public int lengthOfLongestSubstring(String s) {int l = 0 , r = 0 , ans = 0 ;Set<Character> set = new HashSet<>();for(; r < s.length() ;r++){while(set.contains(s.charAt(r))){set.remove(s.charAt(l));l++;}set.add(s.charAt(r));int len = r - l + 1;ans = Math.max(ans,len);}return ans;}

滑动窗口题目汇总:3. 无重复字符的最长子串题解 - 力扣(LeetCode)

75.2. 两数相加 - 力扣(LeetCode)

链表,进位边界情况的处理分析

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {int carry = 0;ListNode head = new ListNode(-1);ListNode p = head;ListNode p1 = l1 , p2 = l2;while(p1 != null && p2 != null){int value = p1.val + p2.val + carry;carry = value/10;p.next = new ListNode(value % 10);p = p.next;p1 = p1.next;p2 = p2.next;}while(p1 != null){int value = p1.val + carry;carry = value/10;p.next = new ListNode(value % 10);p = p.next;p1 = p1.next;}while(p2 != null){int value = p2.val + carry;carry = value/10;p.next = new ListNode(value % 10);p = p.next;p2 = p2.next;}if(carry != 0){p.next = new ListNode(carry);}return head.next;}

时间复杂度O(m+n)

空间复杂度O(m+n)

76.79. 单词搜索 - 力扣(LeetCode)

回溯,dfs

int[][] directions = new int[][]{{1,0},{0,1},{-1,0},{0,-1}};boolean res = false;char[][] board;StringBuilder path = new StringBuilder();String word;boolean[][] isVisited;public boolean exist(char[][] board, String word) {this.board = board;this.word = word;isVisited = new boolean[board.length][board[0].length];char head = word.charAt(0);for(int i = 0 ; i < board.length ; i++){for(int j = 0 ; j < board[0].length ; j++){if(board[i][j] == head){isVisited[i][j] = true;path.append(board[i][j]);dfs(i,j,0);path.deleteCharAt(path.length() - 1);isVisited[i][j] = false;}if(res){return res;}}}return res;}public void dfs(int x , int y , int index){if(word.charAt(index) != path.charAt(path.length() - 1)){//剪枝,减去board[x][y]与word对应字符不匹配的搜索return ;}if(path.toString().equals(word)){res = true;//剪枝,减去找到正确答案以后得搜索return;}for(int i = 0 ; i < 4 ; i++){int newX = x + directions[i][0];int newY = y + directions[i][1];if(newX < 0 || newX >= board.length || newY < 0 || newY >= board[0].length || isVisited[newX][newY]){continue;}isVisited[newX][newY] = true;path.append(board[newX][newY]);dfs(newX,newY,path.length() - 1);path.deleteCharAt(path.length() - 1);isVisited[newX][newY] = false;}}

77.114. 二叉树展开为链表 - 力扣(LeetCode)

I创建一个新的链表,dfs,然后修改树

ListNode head;ListNode list;public void flatten(TreeNode root) {if(root == null) return;head = new ListNode(0);list = head;dfs(root);list = head.next.next;root.left = null;TreeNode node = root;while(list != null){node.right = new TreeNode(list.val);node = node.right;list = list.next;}}public void dfs(TreeNode root){list.next = new ListNode(root.val);list = list.next;if(root.left != null){dfs(root.left);}if(root.right != null){dfs(root.right);}}

II 根据先序遍历中左右的顺序,分析插入的顺序,进行模拟

  1. 将左子树插入到右子树的地方
  2. 将原来的右子树接到左子树的最右边节点
  3. 考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null
    1/ \2   5/ \   \
3   4   6//将 1 的左子树插入到右子树的地方1\2         5/ \         \3   4         6        
//将原来的右子树接到左子树的最右边节点1\2          / \          3   4  \5\6//将 2 的左子树插入到右子树的地方1\2          \          3       4  \5\6   //将原来的右子树接到左子树的最右边节点1\2          \          3      \4  \5\6         ......
public void flatten(TreeNode root) {while(root != null){if(root.left == null){root = root.right;}else{TreeNode pre = root.left;while(pre.right != null){pre = pre.right;}pre.right = root.right;root.right = root.left;root.left = null;root = root.right;}}}

III原地修改

容易想到的方法是在先序遍历的过程中,把当前遍历的结点改成上一结点的右结点就能满足题目要求,但会发现原来还没有访问的右结点丢失了。那么可以想到如果先访问结点,再修改,就能避免修改造成的影响,只要按照右、左、中的顺序进行遍历,即后序遍历,将当前节点的右结点改成上一次访问的结点,即能满足题目要求,并且把左结点置为null,而左结点已经放问过了,不会有影响。

TreeNode pre;public void flatten(TreeNode root) {if(root == null) return;postOrder(root);}public void postOrder(TreeNode root){if(root.right != null){postOrder(root.right);}if(root.left != null){postOrder(root.left);}root.right = pre;root.left = null;pre = root;}

78.621. 任务调度器 - 力扣(LeetCode)

模拟 贪心

贪心:每次选择待执行次数最多的任务

class Solution {public int leastInterval(char[] tasks, int n) {Map<Character, Integer> freq = new HashMap<Character, Integer>();for (char ch : tasks) {freq.put(ch, freq.getOrDefault(ch, 0) + 1);}// 任务种类数int m = freq.size();List<Integer> nextValid = new ArrayList<Integer>();List<Integer> rest = new ArrayList<Integer>();Set<Map.Entry<Character, Integer>> entrySet = freq.entrySet();for (Map.Entry<Character, Integer> entry : entrySet) {int value = entry.getValue();nextValid.add(1);rest.add(value);}int time = 0;for (int i = 0; i < tasks.length; ++i) {++time;int minNextValid = Integer.MAX_VALUE;for (int j = 0; j < m; ++j) {if (rest.get(j) != 0) {minNextValid = Math.min(minNextValid, nextValid.get(j));}}time = Math.max(time, minNextValid);int best = -1;for (int j = 0; j < m; ++j) {if (rest.get(j) != 0 && nextValid.get(j) <= time) {if (best == -1 || rest.get(j) > rest.get(best)) {best = j;}}}nextValid.set(best, time + n + 1);rest.set(best, rest.get(best) - 1);}return time;}
}

另一种贪心策略:

 public int leastInterval(char[] tasks, int n) {//统计每个任务出现的次数,找到出现次数最多的任务int[] hash = new int[26];for(int i = 0; i < tasks.length; ++i) {hash[tasks[i] - 'A'] += 1;}Arrays.sort(hash);//因为相同元素必须有n个冷却时间,假设A出现3次,n = 2,任务要执行完,至少形成AXX AXX A序列(X看作预占位置)//该序列长度为int minLen = (n+1) *  (hash[25] - 1) + 1;//此时为了尽量利用X所预占的空间(贪心)使得整个执行序列长度尽量小,将剩余任务往X预占的空间插入//剩余的任务次数有两种情况://1.与A出现次数相同,比如B任务最优插入结果是ABX ABX AB,中间还剩两个空位,当前序列长度+1//2.比A出现次数少,若还有X,则按序插入X位置,比如C出现两次,形成ABC ABC AB的序列//直到X预占位置还没插满,剩余元素逐个放入X位置就满足冷却时间至少为nfor(int i = 24; i >= 0; --i){if(hash[i] == hash[25]) ++ minLen;}//当所有X预占的位置插满了怎么办?//在任意插满区间(这里是ABC)后面按序插入剩余元素,比如ABCD ABCD发现D之间距离至少为n+1,肯定满足冷却条件//因此,当X预占位置能插满时,最短序列长度就是task.length,不能插满则取最少预占序列长度return Math.max(minLen, tasks.length);}

79.617. 合并二叉树 - 力扣(LeetCode)

递归

1.确定递归函数参数和返回值

参数:两个树的根节点

返回值:合并后树的根节点

2.终止条件

因为是传入了两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了(如果t2也为NULL也无所谓,合并之后就是NULL)。

反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓,合并之后就是NULL)。

3.确定单层逻辑

重复利用一下t1这个树,t1就是合并之后树的根节点(就是修改了原来树的结构)。

那么单层递归中,就要把两棵树的元素加到一起。

t1.val += t2.val;

接下来t1 的左子树是:合并 t1左子树 t2左子树之后的左子树。

t1 的右子树:是 合并 t1右子树 t2右子树之后的右子树。

最终t1就是合并之后的根节点。

public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if(root1 == null) return root2;if(root2 == null) return root1;root1.val += root2.val;root1.left = mergeTrees(root1.left,root2.left);root1.right = mergeTrees(root1.right,root2.right);return root1;}

80.105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。

如果让我们肉眼看两个序列,画一棵二叉树的话,应该分分钟都可以画出来。

流程如图:

那么代码应该怎么写呢?

说到一层一层切割,就应该想到了递归。

来看一下一共分几步:

  • 第一步:如果数组大小为零的话,说明是空节点了。

  • 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。

  • 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点

  • 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)

  • 第五步:切割后序数组,切成后序左数组和后序右数组

  • 第六步:递归处理左区间和右区间

不难写出如下代码:(先把框架写出来)

TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {// 第一步if (postorder.size() == 0) return NULL;// 第二步:后序遍历数组最后一个元素,就是当前的中间节点int rootValue = postorder[postorder.size() - 1];TreeNode* root = new TreeNode(rootValue);// 叶子节点if (postorder.size() == 1) return root;// 第三步:找切割点int delimiterIndex;for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {if (inorder[delimiterIndex] == rootValue) break;}// 第四步:切割中序数组,得到 中序左数组和中序右数组// 第五步:切割后序数组,得到 后序左数组和后序右数组// 第六步root->left = traversal(中序左数组, 后序左数组);root->right = traversal(中序右数组, 后序右数组);return root;
}

难点大家应该发现了,就是如何切割,以及边界值找不好很容易乱套。

此时应该注意确定切割的标准,是左闭右开,还有左开右闭,还是左闭右闭,这个就是不变量,要在递归中保持这个不变量。

首先要切割中序数组,为什么先切割中序数组呢?

切割点在后序数组的最后一个元素,就是用这个元素来切割中序数组的,所以必要先切割中序数组。

中序数组相对比较好切,找到切割点(后序数组的最后一个元素)在中序数组的位置,然后切割,如下代码中我坚持左闭右开的原则:

// 找到中序遍历的切割点
int delimiterIndex;
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {if (inorder[delimiterIndex] == rootValue) break;
}// 左闭右开区间:[0, delimiterIndex)
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
// [delimiterIndex + 1, end)
vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end() );

接下来就要切割后序数组了。

首先后序数组的最后一个元素指定不能要了,这是切割点 也是 当前二叉树中间节点的元素,已经用了。

后序数组的切割点怎么找?

后序数组没有明确的切割元素来进行左右切割,不像中序数组有明确的切割点,切割点左右分开就可以了。

此时有一个很重的点,就是中序数组大小一定是和后序数组的大小相同的(这是必然)。

中序数组我们都切成了左中序数组和右中序数组了,那么后序数组就可以按照左中序数组的大小来切割,切成左后序数组和右后序数组。

代码如下:

// postorder 舍弃末尾元素,因为这个元素就是中间节点,已经用过了
postorder.resize(postorder.size() - 1);// 左闭右开,注意这里使用了左中序数组大小作为切割点:[0, leftInorder.size)
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
// [leftInorder.size(), end)
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());

此时,中序数组切成了左中序数组和右中序数组,后序数组切割成左后序数组和右后序数组。

接下来可以递归了,代码如下:

root->left = traversal(leftInorder, leftPostorder);
root->right = traversal(rightInorder, rightPostorder);

完整代码如下:

class Solution {Map<Integer, Integer> map;  // 方便根据数值查找位置public TreeNode buildTree(int[] inorder, int[] postorder) {map = new HashMap<>();for (int i = 0; i < inorder.length; i++) { // 用map保存中序序列的数值对应位置map.put(inorder[i], i);}return findNode(inorder,  0, inorder.length, postorder,0, postorder.length);  // 前闭后开}public TreeNode findNode(int[] inorder, int inBegin, int inEnd, int[] postorder, int postBegin, int postEnd) {// 参数里的范围都是前闭后开if (inBegin >= inEnd || postBegin >= postEnd) {  // 不满足左闭右开,说明没有元素,返回空树return null;}int rootIndex = map.get(postorder[postEnd - 1]);  // 找到后序遍历的最后一个元素在中序遍历中的位置TreeNode root = new TreeNode(inorder[rootIndex]);  // 构造结点int lenOfLeft = rootIndex - inBegin;  // 保存中序左子树个数,用来确定后序数列的个数root.left = findNode(inorder, inBegin, rootIndex,postorder, postBegin, postBegin + lenOfLeft);root.right = findNode(inorder, rootIndex + 1, inEnd,postorder, postBegin + lenOfLeft, postEnd - 1);return root;}
}

前序和中序遍历的解决方法 和 上面讲解的中序和后序的解决方法思想一样。

Map<Integer,Integer> map = new HashMap<>();int[] preorder;int[] inorder;public TreeNode buildTree(int[] preorder, int[] inorder) {this.preorder = preorder;this.inorder = inorder;for(int i = 0 ; i < inorder.length ; i++){map.put(inorder[i],i);}return findNode(0,preorder.length,0,inorder.length);}public TreeNode findNode(int preBegin,int preEnd,int inBegin,int inEnd){if(preBegin >= preEnd || inBegin >= inEnd){return null;}int index = map.get(preorder[preBegin]);int lenOfLeft = index - inBegin;TreeNode node = new TreeNode(preorder[preBegin]);node.left = findNode(preBegin + 1, preBegin + lenOfLeft + 1,inBegin,index);node.right = findNode(preBegin + lenOfLeft + 1, preEnd,index + 1, inEnd);return node;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/13503.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

114,【6】攻防世界 web wzsc_文件上传

进入靶场 传个桌面有的 直接空白了 我们 访问一下上传的东西 /index 没显示用于解析的.htaccess和.user.ini 文件&#xff0c;还两个都不显示 但上传的时候bp查看状态码是200&#xff0c;意味着上传成功了 别的博主说这是服务器在短时间内就立刻将其删掉了&#xff0c;需…

禅道社区版项目管理软件部署(记录篇)

系统要求&#xff08;这里推荐使用docker容器化方式&#xff09;安装前的准备Docker快速安装最后通过查看地址验证是否部署成功开始界面化安装配置 禅道&#xff08;ZenTao&#xff09;是一款国产开源的项目管理软件&#xff0c;专注于敏捷开发流程&#xff0c;支持 Scrum 和 K…

B站自研的第二代视频连麦系统(上)

导读 本系列文章将从客户端、服务器以及音视频编码优化三个层面&#xff0c;介绍如何基于WebRTC构建视频连麦系统。希望通过这一系列的讲解&#xff0c;帮助开发者更全面地了解 WebRTC 的核心技术与实践应用。 背景 在文章《B站在实时音视频技术领域的探索与实践》中&#xff…

Selenium记录RPA初阶 - 基本输入元件

防止自己遗忘&#xff0c;故作此为记录。 爬取网页基本元件并修改后爬取。 包含元件&#xff1a; elements: dict[str, str] {"username": None,"password": None,"email": None,"website": None,"date": None,"ti…

Ubutun本地部署DeepSeek R1

目录 一、本地部署&终端命令行交互 二、网页端交互 三、参考链接 一、本地部署&终端命令行交互 Ollama 是一个轻量级的大语言模型管理工具&#xff0c;支持 Windows / Mac / Linux。 Ollama官网&#xff1a;Ollama # 下载安装ollama curl -fsSL https://ollama.co…

NacosRce到docker逃逸实战

NacosRce到docker逃逸实战 1、Nacos Derby Rce打入内存马 这个漏洞的原理大家应该都知道&#xff0c; 2.3.2 < Nacos < 2.4.0版本默认derby接口未授权访问&#xff0c;攻击者可利用未授权访问执行SQL语句加载构造恶意的JAR包导致出现远程代码执行漏洞。 在日常的漏洞挖…

保姆级教程Docker部署KRaft模式的Kafka官方镜像

目录 一、安装Docker及可视化工具 二、单节点部署 1、创建挂载目录 2、运行Kafka容器 3、Compose运行Kafka容器 4、查看Kafka运行状态 三、集群部署 四、部署可视化工具 1、创建挂载目录 2、运行Kafka-ui容器 3、Compose运行Kafka-ui容器 4、查看Kafka-ui运行状态 …

[创业之路-286]:《产品开发管理-方法.流程.工具 》-1- IPD两个跨职能团队的组织

IPD&#xff08;集成产品开发&#xff09;中的两个重要跨职能组织是IPMT&#xff08;集成产品管理团队&#xff09;和PDT&#xff08;产品开发团队&#xff09;。 在IPD&#xff08;集成产品开发&#xff09;体系中&#xff0c;IRB&#xff08;投资评审委员会&#xff09;、IPM…

【基于SprintBoot+Mybatis+Mysql】电脑商城项目之修改密码和个人资料

&#x1f9f8;安清h&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;【Spring篇】【计算机网络】【Mybatis篇】 &#x1f6a6;作者简介&#xff1a;一个有趣爱睡觉的intp&#xff0c;期待和更多人分享自己所学知识的真诚大学生。 目录 &#x1f383;1.修改密码 -持久…

02.06 网络编程_概述

思维导图 总图&#xff1a;

初阶数据结构:树---堆

目录 一、树的概念 二、树的构成 &#xff08;一&#xff09;、树的基本组成成分 &#xff08;二&#xff09;、树的实现方法 三、树的特殊结构------二叉树 &#xff08;一&#xff09;、二叉树的概念 &#xff08;二&#xff09;、二叉树的性质 &#xff08;三&#…

【LeetCode】day15 142.环形链表II

142. 环形链表 II - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则…

2025.2.6

一、C思维导图&#xff1a; 二、C&#xff1a; 三、注释代码 1> 配置文件&#xff1a;.pro文件 QT core gui # 引入的类库&#xff0c;core表示核心库 gui图形化界面库greaterThan(QT_MAJOR_VERSION, 4): QT widgets # 超过版本4的qt&#xff0c;会自动加widgets…

CSS(三)less一篇搞定

目录 一、less 1.1什么是less 1.2Less编译 1.3变量 1.4混合 1.5嵌套 1.6运算 1.7函数 1.8作用域 1.9注释与导入 一、less 1.1什么是less 我们写了这么久的CSS,里面有很多重复代码&#xff0c;包括通配颜色值、容器大小。那我们能否通过js声明变量来解决这些问题&…

643. 子数组最大平均数 I

目录 一、题目二、思路2.1 解题思路2.2 代码尝试2.3 疑难问题 三、解法四、收获4.1 心得4.2 举一反三 一、题目 二、思路 2.1 解题思路 和之前一样&#xff0c;用一个sum来存储统计情况&#xff0c;窗口滑动边统计&#xff0c;用两个for循环&#xff0c;一个初始化&#xff0…

go数据结构学习笔记

本博文较为完整的实现了go的链表、栈&#xff0c;队列&#xff0c;树&#xff0c;排序&#xff0c;链表包括顺序链表&#xff0c;双向链表&#xff0c;循环链表&#xff0c;队列是循环队列&#xff0c;排序包含冒牌、选择 1.链表 1.1 顺序链表 type LNode struct {data intn…

机器学习--python基础库之Matplotlib (1) 超级详细!!!

机器学习--python基础库Matplotlib 机器学习--python基础库Matplotlib0 介绍1 实现基础绘图-某城市温度变化图1.1绘制基本图像1.2实现一些其他功能 2 再一个坐标系中绘制多个图像3 多个坐标系显示-plt.subplots(面向对象的画图方法)4 折线图的应用场景 机器学习–python基础库M…

Java 23新特性

文章目录 Java 23新特性一、引言二、Markdown文档注释&#xff08;JEP 467&#xff09;示例 三、ZGC&#xff1a;默认的分代模式&#xff08;JEP 474&#xff09;1. 为什么要引入分代模式2. 使用分代模式的优势3. 如何启用分代模式 四、隐式声明的类和实例主方法&#xff08;JE…

【redis】数据类型之string

字符串类型是Redis最基础的数据结构。首先key都是字符串类型&#xff0c;而且其他几种数据结构都是在字符串类型基础上构建的&#xff0c;所以字符串类型能为其他四种数据结构的学习打下基础。 字符串类型的值实际可以是字符串&#xff08;简单的字符串、复杂的字符串&#xf…