赞
踩
当我们谈论算法时,我们谈论的是解决问题的方法和步骤。在计算机科学中,算法是一系列明确定义的指令,用于执行特定任务或解决特定问题。算法在各个领域都起着至关重要的作用,从数据处理到人工智能,无处不在。
算法可以被视为一种计算过程,通过一系列有序的步骤来执行特定的任务。它是一种数学概念,通常由一组输入、输出和执行步骤组成。一个好的算法应该是正确的、高效的,并且适用于特定问题。
递归是一种通过在算法的执行过程中自我调用的方式来解决问题的方法。在递归算法中,问题被分解为更小的子问题,并通过递归调用相同的算法来解决这些子问题。递归算法通常通过定义一个或多个基本情况(边界条件)来终止递归过程。
这样描述可能有点抽象,下面通过几道例题进行加深你的理解。
递归算法适用于:
一些常见的应用场景包括:
void func() {
if (true) { // 递归结束条件
return // 递归出口
}
func(); // 调用自身
}
请看以下例题加深理解。
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
分析:
则我们有以下解决方案:
如何用递归性质:
递归解法:
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { private int[] res; // 结果数组 public int[] reversePrint(ListNode head) { int len = 0; // 链表长度 func(head, len); // 递归 return res; } private void func(ListNode head, int len) { if (head == null) { res = new int[len]; // 在递归出口初始化结果数组,因为在这才直到链表的长度。 return; } // 最小子问题 len ++; // 每递归一次长度加1 func(head.next, len); res[res.length-len] = head.val; } }
DFS深度优先算法是树的常用遍历算法,由于树是高度递归的,所以DFS深度优先算法常用递归来实现,也可以使用栈来实现深度优先算法。
这里可以让你更加明白为啥递归是一个隐式方法栈。
DFS深度优先算法模板-递归实现:
// 先序遍历 public static void dfSPre(Node treeNode) { if (treeNode == null) { return; } // 遍历节点 process(treeNode) // 遍历左节点 dfSPre(treeNode.left); // 遍历右节点 dfSPre(treeNode.right); } // 后序遍历 public static void dfSPost(Node treeNode) { if (treeNode == null) { return; } // 遍历左节点 dfSPost(treeNode.left); // 遍历右节点 dfSPost(treeNode.right); // 遍历节点 process(treeNode) } // 中序遍历 public static void dfSInner(Node treeNode) { if (treeNode == null) { return; } // 遍历左节点 dfSInner(treeNode.left); // 遍历节点 dfSInner(treeNode) // 遍历右节点 dfSInner(treeNode.right); }
DFS深度优先算法模板-非递归实现:
/** * 使用栈来实现 dfs * @param root */ public static void dfsWithStack(Node root) { if (root == null) { return; } Stack<Node> stack = new Stack<>(); // 先把根节点压栈 stack.push(root); while (!stack.isEmpty()) { Node treeNode = stack.pop(); // 遍历节点 process(treeNode) // 先压右节点 if (treeNode.right != null) { stack.push(treeNode.right); } // 再压左节点 if (treeNode.left != null) { stack.push(treeNode.left); } } }
补充:BFS
/** * 使用队列实现 bfs * @param root */ private static void bfs(Node root) { if (root == null) { return; } Queue<Node> stack = new LinkedList<>(); stack.add(root); //根结点入队 while (!stack.isEmpty()) { Node node = stack.poll(); // 头结点出队 System.out.println("value = " + node.value); // 对结点的操作 Node left = node.left; if (left != null) { // 头结点的左子树入队 stack.add(left); } Node right = node.right; if (right != null) { // 头结点的右子树入队 stack.add(right); } } }
树的高度递归体现在:
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
示例 1:
输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。
分析,利用树的高度递归性:
其子问题简化为:
代码实现:
// 双递归解法,pathSum用于当前节点的遍历,func获取该结点路径。 class Solution { private int res; public int pathSum(TreeNode root, int targetSum) { if (root == null) { return 0; } func(root, (double) targetSum); pathSum(root.left, targetSum); pathSum(root.right, targetSum); return res; } // 先序遍历获取从根到叶节点的路径。 private void func(TreeNode root, double targetSum) { if (root == null) { // 递归出口 return; } if (targetSum-(double) root.val == 0) res++; // 根节点处理 func(root.left, targetSum-root.val); // 递归到左子树处理 func(root.right, targetSum-root.val); // 递归到右子树处理 } }
值得注意的是,递归算法性能大多数时候是极差的。leetcod 437. 路径总和 III 解析,当我们以10为根结点向下遍历时,5->3,5->2的信息我们已经计算过了,然而在递归到以10为根结点时,我们又计算了一遍,因此递归带来的时很多的重复计算的问题,且由于隐式栈的原因,很难进行优化,所以性能极差,因此在实际开发中不要应避免使用递归算法(递归算法可用栈来替代)。
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 2 阶 + 1 阶
分析:
f(n)=f(n-1)+f(n-2)
。代码解:
// 递归方式 class Solution { HashMap<Integer, Integer> hashMap = new HashMap<>(); int climbStairs(int n) { if (n == 1) return 1; if (n == 2) return 2; if (hashMap.get(n) != null) { return hashMap.get(n); }else{ int data = climbStairs(n-1) + climbStairs(n-2); hashMap.put(n, data); // 将之前算过的值存储起来,避免重复计算 return data; } } } // 动态规划方式:数据可拆解为最小子问题,且当前数据可以从之前已经计算过的数据中得到。 class Solution { public: int climbStairs(int n) { int dp[n+1]; if (n<=1) return 1; dp[1]=1; dp[2]=2; for(int i=3;i<n+1;i++){ dp[i]=dp[i-1]+dp[i-2]; // dp[i]为当前数据 } return dp[n]; } };
分治法的设计思想:
分治算法的基本步骤:
分治算法适用于:
一些常见的应用场景包括:
public static void merge_sort(int[] arr) { int len = arr.length; int[] result = new int[len]; merge_sort_recursive(arr, result, 0, len - 1); } static void merge_sort_recursive(int[] arr, int[] result, int start, int end) { if (start >= end) return; int len = end - start, mid = (len >> 1) + start; // 中间结点 int start1 = start, end1 = mid; // 划分左边 int start2 = mid + 1, end2 = end; // 划分 merge_sort_recursive(arr, result, start1, end1); // 递归的对左边划分 merge_sort_recursive(arr, result, start2, end2); // 递归的对右边划分 // 最小规模数据处理 int k = start; while (start1 <= end1 && start2 <= end2) result[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++]; while (start1 <= end1) result[k++] = arr[start1++]; while (start2 <= end2) result[k++] = arr[start2++]; for (k = start; k <= end; k++) arr[k] = result[k]; }
快排思想:
low >= hign
。class quick_sort { int[] arr; // 交换函数 private void swap(int x, int y) { int temp = arr[x]; arr[x] = arr[y]; arr[y] = temp; } private void quick_sort_recursive(int start, int end) { // 最小规模数据处理 if (start >= end) return; int mid = arr[end]; int left = start, right = end - 1; while (left < right) { while (arr[left] <= mid && left < right) left++; while (arr[right] >= mid && left < right) right--; swap(left, right); } if (arr[left] >= arr[end]) swap(left, end); else left++; quick_sort_recursive(start, left - 1); // 递归的对左边划分 quick_sort_recursive(left + 1, end); // 递归的对右边划分 } public void sort(int[] arrin) { arr = arrin; quick_sort_recursive(0, arr.length - 1); } }
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
分治策略:
分治解法代码:
public class Solution { public int maxSubArray(int[] nums) { int len = nums.length; if (len == 0) { return 0; } return maxSubArraySum(nums, 0, len - 1); } private int maxCrossingSum(int[] nums, int left, int mid, int right) { // 一定会包含 nums[mid] 这个元素 int sum = 0; int leftSum = Integer.MIN_VALUE; // 左半边包含 nums[mid] 元素,最多可以到什么地方 // 走到最边界,看看最值是什么 // 计算以 mid 结尾的最大的子数组的和 for (int i = mid; i >= left; i--) { sum += nums[i]; if (sum > leftSum) { leftSum = sum; } } sum = 0; int rightSum = Integer.MIN_VALUE; // 右半边不包含 nums[mid] 元素,最多可以到什么地方 // 计算以 mid+1 开始的最大的子数组的和 for (int i = mid + 1; i <= right; i++) { sum += nums[i]; if (sum > rightSum) { rightSum = sum; } } return leftSum + rightSum; } private int maxSubArraySum(int[] nums, int left, int right) { if (left == right) { return nums[left]; } int mid = left + (right - left) / 2; return max3(maxSubArraySum(nums, left, mid), maxSubArraySum(nums, mid + 1, right), maxCrossingSum(nums, left, mid, right)); } private int max3(int num1, int num2, int num3) { return Math.max(num1, Math.max(num2, num3)); } }
分治解法思路比较简单,但是会经常存在重复计算与合并操作复杂的问题,因此在一些特定问题上,可以采用动态规划的方式进行处理,往往可以用分治法解决的,都可以使用动态规划进行解决,而分治的核心在于最小子问题的思想,往往配合其他算法使用。
动态规划算法是一种常用的问题解决方法。它通过将问题分解为更小的子问题,并保存子问题的解,以避免重复计算。与分治法不同的是,动态规划算法的子问题往往是不相同的且有关联的。
动态规划的核心在于:
这样描述可能有点抽象,下面通过几道例题进行加深你的理解。
动态规划适用于:
一些常见的应用场景包括:
// 对于递推式f(n) = f(n-1) + f(n-2)
void func(int[] nums) {
int[] dp = new int[nums.length];
dp[0] = 0; // 边界条件
dp[1] = 1; // 边界条件
for (int i=3; i< dp.length; i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[nums.length-1];
}
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[100,2,3,100]
输出:200
解释:偷窃 1 号房屋 (金额 = 100) ,然后偷窃 4 号房屋 (金额 = 100)。
偷窃到的最高金额 = 100 + 100 = 200 。
分析:
dp[i] = Math.max(dp[i-2]+nums[i], dp[i-1])
,即相邻的金币比较大,则不偷取当前的房屋,若相隔一间加上当前房屋的数额比相邻的大,则偷取当前房屋,并计算能偷取的最大值。代码实现:
class Solution {
public int rob(int[] nums) {
if (nums.length <= 1) return nums[0];
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(dp[0], nums[1]);
for (int i = 2; i < nums.length; i++){
dp[i] = Math.max(dp[i-2]+nums[i], dp[i-1]);
System.out.println(dp[i]);
}
return dp[nums.length-1];
}
}
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
分析:
dp[i] = preMax + 1;
代码如下:
// 动态规划解法 class Solution { public int lengthOfLIS(int[] nums) { int n = nums.length; if (n == 1) return 1; int[] dp = new int[n]; dp[0] = 1; int res = 1; for (int i = 1; i<n ; i++) { int tempMax = 0; for (int j = 0; j < i; j++) { if (nums[j]< nums[i] && dp[j] > tempMax) { tempMax = dp[j]; } } dp[i] = tempMax + 1; if (dp[i] > res) res = dp[i]; } return res; } }
递增问题还可以由单调栈进行求解,参考第三章。
给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1:
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
分析:
代码实现:
class Solution { public int numSquares(int n) { int[] dp = new int[n+2]; dp[1] = 1; dp[2] = 2; for (int i = 3; i<=n ; i++) { int temp = (int)Math.sqrt(i); int index = (int)Math.pow(temp,2); if (index == i) { dp[i] = 1; } else{ int tempMin = n; for (int j = i/2; j<=index ; j++){ tempMin = Math.min(dp[j] + dp[i-j], tempMin); } dp[i] = tempMin; } } return dp[n]; } }
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
分析:
dp[i] = Math.min(dp[i-coins[j]]+1, dp[i])
,举个例子: 11 块钱的硬币数,可以由 6 块钱的硬币数目加一得到。代码实现:
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
for (int i = 1; i <= amount; i++) {
dp[i] = amount + 1;
for (int j = 0; j < coins.length; j++){
if (coins[j] <= i){
dp[i] = Math.min(dp[i-coins[j]]+1, dp[i]);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}
给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
子数组 是数组的连续子序列。
示例 1:
输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
分析:
dp[i][0] = Math.max(nums[i] * dp[i-1][0], Math.max(nums[i], dp[i-1][1]* nums[i]));
即,如果nums[i] * dp[i-1][0]是正数,则赋值,否则取Math.max(nums[i], dp[i-1][1]* nums[i]) (取该值与负数最大值乘以该值的最大值)。dp[i][1] = Math.min(nums[i] * dp[i-1][1], Math.min(nums[i], dp[i-1][0]* nums[i]));
即,如果nums[i] * dp[i-1][0]是负数,则赋值,否则取Math.min(nums[i], dp[i-1][0]* nums[i]) (取该值与负数最大值乘以该值的最小值)。贪心算法的核心思想是在每一步中**都选择当前状态下的最优解,而不考虑全局后果。**它通常适用于那些问题,其最优解可以通过一系列局部最优解的组合得到。
贪心算法的一般步骤如下:
贪心算法的关键是选择合适的贪心策略,以确保每一步都是局部最优的。然而,需要注意的是,贪心算法并不总能得到全局最优解,因此在应用时需谨慎考虑问题的性质。
这样描述可能有点抽象,下面通过几道例题进行加深你的理解。
递归算法适用于:
一些常见的应用场景包括:
给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
分析:
max = Math.max(max, nums[i] + i)
。i == max
,则表示后续的位置便到达不了了。代码实现:
// 贪心算法解法
class Solution {
public boolean canJump(int[] nums) {
if (nums.length == 1) return true;
int max = 0; // max 表示当前可以跳到的最大距离
for (int i = 0; i < nums.length - 1; i++) {
if (nums[i] + i > max) max = nums[i] + i;
if (i == max) return false;
}
return true;
}
}
给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。
每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。
示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
示例 2:
输入: nums = [2,3,0,1,4]
输出: 2
分析:
max = Math.max(max, nums[i] + i)
。代码实现:
class Solution { public int jump(int[] nums) { int res = 0; int max = 0; // 从当前可跳跃的最大值 int end = 0; // 上一个最大值结束的位置 for (int i = 0; i < nums.length - 1; i++) { if (nums[i] + i > max) { max = nums[i] + i; } if (i == end) { // 当遍历到上一个最大值位置时,需要再跳一次,且更新下个能跳的最大值 end = max; res ++; } } return res; } }
回溯算法是一种深度优先的搜索技术,它遵循一种“试错”的思路。在解决问题的过程中,我们通过选择某个路径并探索下去,然后发现当前选择并不是最优或者不符合约束条件时,就返回上一步重新选择路径,直到找到问题的解或者遍历完所有可能的路径。
回溯算法的步骤:
这样描述可能有点抽象,下面通过几道例题进行加深你的理解。
回溯算法的应用领域 回溯算法可以用于解决各种问题,尤其是那些具有以下特征的问题:
回溯算法可以看作森林的遍历过程。
简化成一个二维坐标:
模板代码:
List<?> result = new ArrayList<>();
void backtrack(路径, 选择列表):
if (满足结束条件){
result.add(路径)
return
}
for (选择 in 选择列表){ // x
做选择
backtrack(路径, 选择列表) // y
撤销选择
}
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。
你可以按 任意顺序 返回这些组合。candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 对于给定的输入,保证和为 target 的不同组合数少于 150 个。
示例 1: 输入:candidates = [2,3,6,7], target = 7 输出:[[2,2,3],[7]] 解释: 2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 7 也是一个候选, 7 = 7 。 仅有这两种组合。 示例 2: 输入: candidates = [2,3,5], target = 8 输出: [[2,2,2,2],[2,3,3],[3,5]] 示例 3: 输入: candidates = [2], target = 1 输出: []
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""
输出:[]
示例 3:
输入:digits = "2"
输出:["a","b","c"]
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:
输入:n = 1
输出:["()"]
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a"
输出:[["a"]]
按照国际象棋的规则:
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
示例 1:
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
示例 2:
输入:n = 1
输出:[["Q"]]
Java 对象一切都是引用,其只能对其引用进行操作,使用等号会更改引用的对象。参考对链表的操作。
先 get() 再 put()
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
思路:
num[i]
刚好与key相等,即value存在时,return代码
class Solution { public int[] twoSum(int[] nums, int target) { HashMap<Integer, Integer> map = new HashMap<Integer, Integer>(); for (int i = 0; i < nums.length; i++) { int t = map.getOrDefault(nums[i],-1); map.put(target - nums[i], i); if (t >= 0 && i != t) { int[] res = new int[2]; res[0] = i; res[1] = t; return res; } } return new int[0]; } }
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
思路:
以num[i]、nums[i] - left、nums[i] + right 为key,len 为 value
。其中left、right为已经遍历过数据的最大连续长度,len = left + right + 1
。代码
class Solution { public int longestConsecutive(int[] nums) { if (nums.length <= 1) return nums.length; int res = 0; HashMap<Integer, Integer> hashMap = new HashMap<>(); for (int i = 0; i < nums.length; i++){ if (!hashMap.containsKey(nums[i])){ Integer left = hashMap.getOrDefault(nums[i]-1, 0); Integer right = hashMap.getOrDefault(nums[i]+1, 0); Integer len = left + right + 1; res = Math.max(res, len); hashMap.put(nums[i], len); hashMap.put(nums[i] - left, len); hashMap.put(nums[i] + right, len); } } return res; } }
核心:
if (a > b) low++;
else high++;
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]] 解释: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。 nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。 注意,输出的顺序和三元组的顺序并不重要。 示例 2: 输入:nums = [0,1,1] 输出:[] 解释:唯一可能的三元组和不为 0 。 示例 3: 输入:nums = [0,0,0] 输出:[[0,0,0]] 解释:唯一可能的三元组和为 0 。
思路:
low = i +1,high = nums.length -1
。if (nums[i] + nums[low] + nums[high] < 0){ low++;}
else if (nums[i] + nums[low] + nums[high] > 0){ high--; }
else{ 添加到结果数组}
代码:
class Solution { public List<List<Integer>> threeSum(int[] nums) { Arrays.sort(nums); List<List<Integer>> res = new LinkedList<>(); for (int i = 0; i < nums.length-2; i++) { int low = i+1; int high = nums.length - 1; if (i != 0 && nums[i] == nums[i-1]){ continue; } if (nums[i]+ nums[i+1] + nums[i+2] > 0){ continue; } if (nums[i] + nums[nums.length-1] + nums[nums.length-2] < 0){ continue; } while (low < high) { if (nums[i] + nums[low] + nums[high] < 0){ low++; }else if (nums[i] + nums[low] + nums[high] > 0){ high--; }else{ List<Integer> temp = new LinkedList<>(); temp.add(nums[i]); temp.add(nums[low]); temp.add(nums[high]); res.add(temp); low ++; while (low < high && nums[low] == nums[low-1]){ // 若添加到结果数组时上一个数和现在一样,忽略(去重)。 low++; } high --; while (low < high && nums[high] == nums[high+1]){ high--; } } } } return res; } }
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
思路:
if (height[low] < height[high]){low++;}
代码
lass Solution { public int maxArea(int[] height) { int low = 0, high = height.length -1; int res = 0; while (low != high) { int content = Math.min(height[low], height[high]) * (high - low); if (content > res) res = content; if (height[low] < height[high]){ low++; }else{ high--; } } return res; } }
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
输入:height = [4,2,0,3,2,5]
输出:9
思路:
low、high
, 与两个临时变量。代码:
class Solution { public: int trap(vector<int>& height) { int preMax = 0; // 左边边界最大值,初始为零 int aftMax = 0; // 右边边界最大值,初始为零 int left = 0; int right = height.size() -1; int ans = 0; while (left <= right){ if (height[left] > preMax) preMax = height[left]; if (height[right] > aftMax) aftMax = height[right]; if (preMax < aftMax) { ans += preMax - height[left]; // 取最小的边界减去当前高度 即为容积。 left++; }else{ ans += aftMax - height[right]; right--; } } return ans; } };
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
思路:
代码:
String[] strArray = [];
ArrayList<String> list = new ArrayList<String>(Arrays.asList(strArray)); // 数组转 ArrayList
LinkedList<String> list = new LinkedList<String>(Arrays.asList(strArray));
int[] intArray = arrayList.stream().mapToInt(Integer::intValue).toArray(); // ArrayList<Integer> 转 int数组
Integer[] integerArray = arrayList.toArray(new Integer[0]); // ArrayList<Integer> 转 Integer数组
stream.filter(x -> x > 5) //保留大于 5 的元素。
stream.map(x -> x * 2) // 将每个元素都乘以 2。
stream.distinct() // 将保留唯一的元素。
stream.sorted() // 将按照默认的自然顺序进行排序。
int[] arr = new int[5];//新建一个大小为5的数组
Arrays.fill(arr,4);//给所有值赋值4
String str = Arrays.toString(arr); // Arrays类的toString()方法能将数组中的内容全部打印出来
System.out.print(str);
//输出:[4, 4, 4, 4, 4]
int[] arr = {3,2,1,5,4};
Arrays.sort(arr,0,3);//给第0位(0开始)到第3位(不包括)排序
String str = Arrays.toString(arr); // Arrays类的toString()方法能将数组中的内容全部打印出来
System.out.print(str);
//输出:[1, 2, 3, 5, 4]
int[] arr = {10,20,30,40,50};
System.out.println(Arrays.binarySearch(arr, 30));
//输出:2 (下标索引值从0开始)
int[] arr = {10,20,30,40,50};
System.out.println(Arrays.toString(arr));
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
char charAt(int index)
int compareTo(String anotherString)
注:字符串类不能直接比较,但是char 类型可以直接比较(按照ASCILL编码比较)。
int length()
String substring(int beginIndex, int endIndex)
char[] toCharArray()
// number = "-7"
int result = Integer.parseInt(number);
Integer result2 = Integer.valueOf(number);
boolean isLetter(char ch)
public static boolean isDigit(char ch)
public String toUpperCase()
public String toLowerCase()
String[] split(String regex)
Stack<Integer> stack = new Stack<>();
stack.push(item);
int topElement = stack.peek();
int poppedElement = stack.pop();
int size = stack.size();
Queue<String> queue = new LinkedList<>();
queue.add(item);
String headElement = queue.peek();
String removedElement = queue.poll(); //remove()
int size = stack.size();
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "Google");
String value = map.get(1);
String value = map.getOrDefault(1, ""); //找不到设置默认值
map.remove(1);
map.forEach((key, value) -> {System.out.print(key + ":" + value);});
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。