刷题篇--热题HOT 41-51
102.二叉树的层序遍历
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
分析:使用队列
1 /**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode(int x) { val = x; }
8 * }
9 */
10 class Solution {
11 public List<List<Integer>> levelOrder(TreeNode root) {
12 Queue<TreeNode> queue = new LinkedList();
13 List<List<Integer>> res = new ArrayList();
14 if(root == null) return res;
15 queue.add(root);
16 int levelLength = 0;//每层节点数
17 while(!queue.isEmpty()){
18 List<Integer> list = new ArrayList();
19 levelLength = queue.size();
20 for(int i=0;i<levelLength;i++){
21 TreeNode node = queue.poll();
22 list.add(node.val);
23 if(node.left!=null) queue.add(node.left);
24 if(node.right!=null) queue.add(node.right);
25 }
26 res.add(list);
27 }
28 return res;
29 }
30 }
//递归
1 class Solution {
2 List<List<Integer>> res = new ArrayList();
3 public List<List<Integer>> levelOrder(TreeNode root) {
4 if(root == null) return res;
5 helper(root,0);
6 return res;
7 }
8 public void helper(TreeNode node, int level){
9 //开启新的list
10 if(res.size() == level){
11 res.add(new ArrayList<Integer>());
12 }
13 //加入新的元素
14 res.get(level).add(node.val);
15 //从左往右递归
16 if(node.left != null) helper(node.left, level+1);
17 if(node.right != null) helper(node.right, level+1);
18 }
19 }
104.二叉树的最大深度
法一:递归,当前节点最大深度 = max{左子树最大深度,右子树最大深度} + 1.
1 /**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode(int x) { val = x; }
8 * }
9 */
10 class Solution {
11 public int maxDepth(TreeNode root) {
12 if(root == null) return 0;
13 return 1+Math.max(maxDepth(root.left), maxDepth(root.right));
14 }
15 }
法二:BFS,每层加一
1 class Solution {
2 public int maxDepth(TreeNode root) {
3 if(root == null) return 0;
4 int level = 0;
5 int levelLength = 0;
6 Queue<TreeNode> queue = new LinkedList();
7 queue.add(root);
8 while(!queue.isEmpty()){
9 level++;
10 levelLength = queue.size();
11 for(int i=0;i<levelLength;i++){
12 TreeNode node = queue.poll();
13 if(node.left!=null) queue.add(node.left);
14 if(node.right!=null) queue.add(node.right);
15 }
16 }
17 return level;
18 }
19 }
105.从前序和中序遍历序列构造二叉树
注意:你可以假设树中没有重复的元素。例如,给出前序遍历 preorder = [3,9,20,15,7],中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
分析:前序遍历第一个元素3是根节点,将再根据中序遍历2的位置将inorder分成左子树9和右子树15,20,7。然后再根据preorder判断,9为左子树根节点,20为右子树根节点,则可以判断15是20的左子树,7是20的右子树。
Arrays.copyOfRange(arr, i
, j
);复制数组,包括索引i,不包括索引j;
1 /**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode(int x) { val = x; }
8 * }
9 */
10
11 class Solution {
12 public TreeNode buildTree(int[] preorder, int[] inorder) {
13 if(preorder.length == 0 || inorder.length == 0) return null;
14 TreeNode root = new TreeNode(preorder[0]);
15 for(int i=0;i<preorder.length;i++){
16 if(preorder[0] == inorder[i]){
17 root.left = buildTree(Arrays.copyOfRange(preorder,1,i+1),Arrays.copyOfRange(inorder,0,i));
18 root.right = buildTree(Arrays.copyOfRange(preorder,i+1,preorder.length),Arrays.copyOfRange(inorder,i+1,inorder.length));
19 break;
20 }
21
22 }
23 return root;
24 }
25 }
114.二叉树展开为链表
给定一个二叉树,原地将它展开为链表。
例如,给定二叉树
1 / \ 2 5 / \ \ 3 4 6
将其展开为:
1
\
2
\
3
\
4
\
5
\
6
分析:原地转换,即把left、right变换节点。示意图是通过前序遍历顺序1,2,3,4,5,6改变指向。由此我们可以想到前序遍历过程中进行操作,每次遍历到一个结点,就将此节点连接至上一结点的右节点,可是这样操作原右节点便无法遍历到了,此时应该如何?如果后序遍历呢?这是可以的,因为6-5-4-3-2-1,每次遍历到一个结点,就将该节点的右节点指向上一个访问的结点,左节点置为null,这样从后往前遍历修改后的结点不会再使用了。因此是可行的。
1 /** 2 * Definition for a binary tree node. 3 * public class TreeNode { 4 * int val; 5 * TreeNode left; 6 * TreeNode right; 7 * TreeNode(int x) { val = x; } 8 * } 9 */ 10 class Solution { 11 TreeNode pre = null;//用于保存上一次遍历的节点 12 public void flatten(TreeNode root) { 13 if(root == null) return; 14 flatten(root.right); 15 flatten(root.left); 16 root.right = pre; 17 root.left = null; 18 pre = root; 19 } 20 }
法二:示意图,整个过程可以看作将当前结点右子树连接到当前节点左子树的最右结点下,然后将当前节点左子树连接到当前节点的右子树,不断变换。
1 //将 1 的左子树插入到右子树的地方 2 1 3 \ 4 2 5 5 / \ \ 6 3 4 6 7 //将原来的右子树接到左子树的最右边节点 8 1 9 \ 10 2 11 / \ 12 3 4 13 \ 14 5 15 \ 16 6 17 18 //将 2 的左子树插入到右子树的地方 19 1 20 \ 21 2 22 \ 23 3 4 24 \ 25 5 26 \ 27 6 28 29 //将原来的右子树接到左子树的最右边节点 30 1 31 \ 32 2 33 \ 34 3 35 \ 36 4 37 \ 38 5 39 \ 40 6
1 class Solution { 2 public void flatten(TreeNode root) { 3 while(root != null){ 4 if(root.left == null){//左子树为空,直接考虑右子树 5 root = root.right; 6 }else{ 7 //找到左子树最右结点 8 TreeNode node = root.left; 9 while(node.right!=null){ 10 node = node.right; 11 } 12 //将当前节点右子树连接到左子树最右结点 13 node.right = root.right; 14 //将当前节点左子树连接到当前节点右子树位置 15 root.right = root.left; 16 //将当前节点左子树置为空 17 root.left = null; 18 ////考虑下一节点 19 root = root.right; 20 } 21 } 22 } 23 }
121.买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。注意你不能在买入股票前卖出股票。
示例 1:输入: [7,1,5,3,6,4] 输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:输入: [7,6,4,3,1] 输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
分析:理解题意后,可以题目理解为数组中当前元素prices[i]和之后数据的最大差值。
法一:暴力,双重循环
1 class Solution { 2 public int maxProfit(int[] prices) { 3 int max = 0; 4 for(int i=0;i<prices.length;i++){ 5 for(int j=i;j<prices.length;j++){ 6 int dValue = prices[j] - prices[i]; 7 if(dValue>0){ 8 max = Math.max(dValue,max); 9 } 10 } 11 } 12 return max; 13 } 14 }
法二:一次遍历,维持两个变量,minPrice,maxProfile分别代表迄今为止的最小谷值和最大利润。
1 class Solution { 2 public int maxProfit(int[] prices) { 3 if(prices == null || prices.length == 0) return 0; 4 int minPrice = prices[0]; 5 int maxProfit = 0; 6 for(int i=1;i<prices.length;i++){ 7 int dValue = prices[i] - minPrice; 8 if(dValue <= 0){ 9 minPrice = prices[i]; 10 }else{ 11 maxProfit = Math.max(maxProfit, dValue); 12 } 13 } 14 return maxProfit; 15 } 16 }
124. 二叉树中最大路径和
给定一个非空二叉树,返回其最大路径和。本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。
示例 1:输入: [1,2,3],输出: 6
1
/ \
2 3
示例 2:输入: [-10,9,20,null,null,15,7],输出: 42
-10
/ \
9 20
/ \
15 7
分析:递归求和,更新最大值maxSum = max{maxSum,left+right+root} (其实如果是根节点的话,left和right等于0).
1 /** 2 * Definition for a binary tree node. 3 * public class TreeNode { 4 * int val; 5 * TreeNode left; 6 * TreeNode right; 7 * TreeNode(int x) { val = x; } 8 * } 9 */ 10 class Solution { 11 int maxSum = Integer.MIN_VALUE; 12 public int maxPathSum(TreeNode root) { 13 helper(root); 14 return maxSum; 15 } 16 public int helper(TreeNode root){ 17 if(root == null) return 0; 18 int leftSum = Math.max(helper(root.left),0);//如果小于0则不选 19 int rightSum = Math.max(helper(root.right),0); 20 maxSum = Math.max(maxSum, root.val+leftSum+rightSum);//如果只选取以当前节点为根节点的路径,更新最大值 21 return root.val + Math.max(leftSum , rightSum);//如果还要接着走,左右只能选取一条道路 22 } 23 }
128.最长连续序列
给定一个未排序的整数数组,找出最长连续序列的长度。要求算法的时间复杂度为 O(n)。
示例:输入: [100, 4, 200, 1, 3, 2] 输出: 4 解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。
分析:对时间有要求就只能牺牲空间了,可以
虽然下列代码中for循环里嵌套了一个while循环,但是每个元素最多被遍历2次 O(2n)~O(n),因为只从最小的开始判断计数。例如极端情况
[98765432],只有遍历到2的时候才开始判断计数,数组中的数字只被遍历2次,满足题意。
1 class Solution { 2 public int longestConsecutive(int[] nums) { 3 int res = 0; 4 Set<Integer> set = new HashSet(); 5 for(int num : nums){ 6 set.add(num); 7 } 8 for(int num : nums){ 9 //只从最小的开始找 10 if(!set.contains(num-1)){ 11 int count = 1; 12 while(set.contains(num + 1)){ 13 count ++; 14 num ++; 15 } 16 res = Math.max(res, count); 17 } 18 } 19 return res; 20 } 21 }
136.只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:输入: [2,2,1]输出: 1
示例 2:输入: [4,1,2,1,2]输出: 4
分析:异或 参考剑指offer
1 class Solution { 2 public int singleNumber(int[] nums) { 3 int res = 0; 4 for(int i=0;i<nums.length;i++){ 5 res = res^nums[i]; 6 } 7 return res; 8 } 9 }
139.单词拆分
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明: 拆分时可以重复使用字典中的单词。你可以假设字典中没有重复的单词。
示例 1:输入: s = "leetcode", wordDict = ["leet", "code"] 输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
示例 2:输入: s = "applepenapple", wordDict = ["apple", "pen"]输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。注意你可以重复使用字典中的单词。
示例 3:输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]输出: false
分析:动态规划,建立数组dp[i]表示字符串s到i未知是否可以由wordDict组成,dp[i+1] = dp[j] &&(s[i:j]是否在wordDict里) j是当前子字符串的拆分位置。拆成为0:j ,j+1:i。
1 class Solution { 2 public boolean wordBreak(String s, List<String> wordDict) { 3 int sLen = s.length(); 4 boolean[] dp = new boolean[sLen+1]; 5 dp[0] = true; 6 for(int i=1;i<=sLen;i++){ 7 for(int j=0;j<i;j++){ 8 if(dp[j]&&wordDict.contains(s.substring(j,i))){ 9 dp[i] = true; 10 break; 11 } 12 } 13 } 14 return dp[sLen]; 15 } 16 }
140.环形链表
给定一个链表,判断链表中是否有环。为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:输入:head = [3,2,0,-4], pos = 1 输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:输入:head = [1,2], pos = 0输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:输入:head = [1], pos = -1输出:false
解释:链表中没有环。
分析:剑指offer,设置两个指针,
1 /** 2 * Definition for singly-linked list. 3 * class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { 7 * val = x; 8 * next = null; 9 * } 10 * } 11 */ 12 public class Solution { 13 public boolean hasCycle(ListNode head) { 14 if(head == null || head.next == null) return false; 15 //设置快慢指针,慢指针一次走一步,快指针一次走两步 16 ListNode slow = head; 17 ListNode fast = head.next; 18 while(slow!=fast){ 19 if(fast == null || fast.next == null){//走到头了还没遇到 20 return false; 21 } 22 slow = slow.next; 23 fast = fast.next.next; 24 } 25 return true; 26 } 27 }
142.环形链表Ⅱ
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。说明:不允许修改给定的链表。
1 /** 2 * Definition for singly-linked list. 3 * class ListNode { 4 * int val; 5 * ListNode next; 6 * ListNode(int x) { 7 * val = x; 8 * next = null; 9 * } 10 * } 11 */ 12 public class Solution { 13 public ListNode detectCycle(ListNode head) { 14 //1.判断是否有环 15 if(head == null || head.next == null) return null; 16 //设置快慢指针,慢指针一次走一步,快指针一次走两步 17 ListNode slow = head; 18 ListNode fast = head.next; 19 while(slow!=fast){ 20 if(fast == null || fast.next == null){//走到头了还没遇到 21 return null; 22 } 23 slow = slow.next; 24 fast = fast.next.next; 25 } 26 27 //2.计算环内有几个节点 28 //经过第一步结束时,fast slow在环内相遇,让fast指针不动,slow指针循环一圈,便可求得节点数 29 int count = 1; 30 slow = slow.next; 31 while(fast!=slow){ 32 slow = slow.next; 33 count++; 34 } 35 36 //3.设置两个指针,一个先走count步,再次相遇时便是第一个环结点 37 fast = head; 38 slow = head; 39 for(int i=0;i<count;i++){ 40 fast = fast.next; 41 } 42 while(fast!=slow){ 43 fast = fast.next; 44 slow = slow.next; 45 } 46 return fast; 47 } 48 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步