本文主要是介绍LeetCode 热题 HOT 100(P11~P20),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
LC020valid_parentheses
. - 力扣(LeetCode)
题目:
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
解法:
遇到右括号的时候需要判断有没有对应匹配的左括号,因为必须是类型相同的才能进行闭合,因此需要一个栈来维护遇到的左括号,看栈顶的左括号是否跟当前的右括号匹配。
static Map<Character, Character> cache = new HashMap<>();static {cache.put(')', '(');cache.put('}', '{');cache.put(']', '[');}public boolean isValid(String s) {LinkedList<Character> stack = new LinkedList<>();for (char c : s.toCharArray()) {// 遇到右括号就需要一定要有匹配的左括号if (cache.containsKey(c)) {if (stack.isEmpty()) {return false;}if (cache.get(c) != stack.pop()) {return false;}} else {//左括号的情况,直接入栈stack.push(c);}}return stack.isEmpty();}
这里有个技巧,维护一个右括号为key 的map,这样方便判断和匹配。
LC021merge_two
. - 力扣(LeetCode)
题目:
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
解法:
遇到链表题,直接创建一个pre 节点,接着轮流比较两边的大小,比较繁琐的是需要判断很多临界情况。
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {ListNode pre = new ListNode();ListNode cur = pre;while (list1 != null || list2 != null) {ListNode next;if (list1 == null) {next = list2;list2 = list2.next;} else if (list2 == null) {next = list1;list1 = list1.next;} else {if (list1.val < list2.val) {next = list1;list1 = list1.next;} else {next = list2;list2 = list2.next;}}cur.next = next;cur = cur.next;}return pre.next;}
LC022generate_parentheses
. - 力扣(LeetCode)
题目:
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
题解:
一般这种题都是递归的思路,相当于有2n个位置,每个位置都可能至多有2种策略,不是放左括号就是放右括号,放好之后再接着放下一个。这里有个有效括号的限制,所以想清楚这个就相对容易了。
List<String> result = new ArrayList<>();public List<String> generateParenthesis(int n) {dsf("", n, n);return result;}/*** 递归的写法** @param path 当前的字符串* @param left 左括号剩余使用次数* @param right 右括号剩余使用次数*/private void dsf(String path, int left, int right) {// 错误的情况if (left > right) {return;}if (left == 0 && right == 0) {result.add(path);return;}if (left > 0) {dsf(path + "(", left - 1, right);}if (right > 0) {dsf(path + ")", left, right - 1);}}
这里使用剩余使用测试,而不是已经使用次数,会让代码更加清晰。
递归的写法在熟悉递归模板之后,就是对什么时候跳出递归的判断,这一步思考清楚,后面就不难了。
LC023merge_k
. - 力扣(LeetCode)
题目:
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
题解:
其实就是利用递归的思路,先对半往下递归,最后是两个链表的合并(LC021merge_two )。
public ListNode mergeKLists(ListNode[] lists) {if (lists.length == 0) {return null;}return merge(lists, 0, lists.length - 1);}private ListNode merge(ListNode[] lists, int left, int right) {if (left == right) {return lists[left];}int mid = left + (right - left) / 2;final ListNode l1 = merge(lists, left, mid);final ListNode l2 = merge(lists, mid + 1, right);return merge2(l1, l2);}private ListNode merge2(ListNode l1, ListNode l2) {if (l1 == null) {return l2;}if (l2 == null) {return l1;}if (l1.val < l2.val) {l1.next = merge2(l1.next, l2);return l1;} else {l2.next = merge2(l1, l2.next);return l2;}}
递归真是好东西啊,任何复杂的问题都能化繁为简。
LC031next_permutation
. - 力扣(LeetCode)
题目:
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
给你一个整数数组 nums ,找出 nums 的下一个排列。
题解:
这类题目就是典型的套路题了,当初靠自己想破脑袋估计也想不出来,直接看题解知道套路,掌握思路后加上自己在事例上推演下也就能写出来了。
public void nextPermutation(int[] nums) {final int len = nums.length;if (len < 2) {return;}int i = len - 2, k = len - 1;//找到最右边的“小数”while (i >= 0 && nums[i] >= nums[i + 1]) {i--;}//如果不是最后一种情况,注意这里包含=0 的情况if (i >= 0) {//从后往前,找到尽可能“小”的大数while (nums[i] >= nums[k]) {k--;}int tmp = nums[i];nums[i] = nums[k];nums[k] = tmp;}//需要将i+1 之后的数字排序//将「大数」换到前面后,需要将「大数」后面的所有数 重置为升序,升序排列就是最小的排列。Arrays.sort(nums, i + 1, len);}
LC032longest_valid
. - 力扣(LeetCode)
题目:
给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度
题解:
“最*** ” 的基本是动态规划的思路。只是这里情况比较复杂,需要考虑很多边界情况
public int longestValidParentheses(String s) {final char[] chars = s.toCharArray();int[] dp = new int[chars.length];int max = 0;for (int i = 1; i < chars.length; i++) {//只有封闭的右括号才有可能配对if (chars[i] == ')') {//跟前一位刚好匹配if (chars[i - 1] == '(') {// 数量就是前前位+2,这里>= 还是> 实际是一样的,因为dp[0]=0dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;} else { // i-1 也是右括号final int pre = i - 1 - dp[i - 1];if (pre >= 0 && chars[pre] == '(') {dp[i] = (pre > 0 ? dp[pre - 1] : 0) + dp[i - 1] + 2;}}max = Math.max(max, dp[i]);}}return max;}
这个也可以算套路题吧,动态规划还是挺考验功力的,动态方程往往取决动态数组的定义。这类题只能靠多做,多培养感觉了。
LC033search_in
. - 力扣(LeetCode)
题目:
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
题解:
经典的二分查找啊,之前刚学二分查找的时候觉得这玩意还不简单,没有特意准备,结果是啪啪打脸了。先复习下二分查找模板:
left, right = 0, len(array) - 1
/注意这里的=
while left <= right:mid = left+(right - left) / 2 if array[mid] == target:# find the target!!break or return result elif array[mid] < target:left = mid + 1 else:right = mid - 1
回到这道题,关键点是旋转数组始终有一半是有序的,这个时候针对有序的一边进行判断和处理,如果不在这一边就说明在另一边,说明当前这一边可以舍弃,继续下一步迭代。
public int search(int[] nums, int target) {if (nums.length == 0) {return -1;}int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] == target) {return mid;}//左边有序的情况if (nums[left] <= nums[mid]) {if (target >= nums[left] && target <= nums[mid]) {right = mid - 1;} else {left = mid + 1;}} else {if (target > nums[mid] && target <= nums[right]) {left = mid + 1;} else {right = mid - 1;}}}return -1;}
LC34find_first
. - 力扣(LeetCode)
题目:
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
题解:
这道题比较有意思,一般我们通过折半查找都是确定具体的位置,这里是要找做边界和右边界,实际跟折半查找非常类似,只是左边界往小的那一边继续找,右边界就往大的那一边继续找。
public int[] searchRange(int[] nums, int target) {final int len = nums.length;if (len == 0) {return new int[]{ -1, -1 };}int left = 0, right = len - 1;int begin = -1, end = -1;//先找左边起始点while (left <= right) {int mid = left + (right - left) / 2;//要找左边界,需要往尽可能小的方向寻找if (nums[mid] == target) {begin = mid;right = mid - 1;} else if (target > nums[mid]) {left = mid + 1;} else {right = mid - 1;}}left = 0;right = len - 1;while (left <= right) {int mid = left + (right - left) / 2;//要找右边界,需要往尽可能大的方向寻找if (nums[mid] == target) {end = mid;left = mid + 1;} else if (target > nums[mid]) {left = mid + 1;} else {right = mid - 1;}}return new int[]{ begin, end };}
可以看到有挺多重复代码,可以提一个方法出来
public int[] searchRange(int[] nums, int target) {int left = 0, right = nums.length - 1;int begin = getLocation(nums, target, left, right, true);int end = getLocation(nums, target, left, right, false);return new int[]{ begin, end };}/*** 找到target 对应的下标** @param nums* @param target* @param left* @param right* @param isBegin 是否起始点* @return*/private int getLocation(int[] nums, int target, int left, int right, boolean isBegin) {int location = -1;while (left <= right) {int mid = left + (right - left) / 2;if (target == nums[mid]) {location = mid;//关键点在于找起止点的时候对左右游标的处理if (isBegin) {right = mid - 1;} else {left = mid + 1;}} else if (target < nums[mid]) {right = mid - 1;} else {left = mid + 1;}}return location;}
LC39combination_sum
. - 力扣(LeetCode)
题目:
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
题解:
也是典型的递归思路,关键是怎么保证不走回头路,比如有整数数组是(1,2,3),在递归1 的时候,把所有可以跟1组合的所有数组都穷举了,那么递归到2 的时候,就不能再用1 了,因为这个组合肯定在之前递归1的时候已经存在了。
public List<List<Integer>> combinationSum(int[] candidates, int target) {if (candidates.length == 0) {return result;}dfs(candidates, new ArrayList<>(), 0, target);return result;}/*** 注意这里需要一个start 标识,用来避免走回头路引起的重复数据** @param candidates* @param path* @param start* @param target*/private void dfs(int[] candidates, ArrayList<Integer> path, int start, int target) {if (target == 0) {result.add(new ArrayList<>(path));return;}if (target < 0) {return;}for (int i = start; i < candidates.length; i++) {path.add(candidates[i]);dfs(candidates, path, i, target - candidates[i]);path.remove(path.size() - 1);}}
LC46permutations
. - 力扣(LeetCode)
题目:
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
题解:
也是用递归解决,难点在于去重。这里通过一个boolean 数组来标识已经使用过的数字。这里可以复习下上一篇的递归模板。
List<List<Integer>> result = new ArrayList<>();public List<List<Integer>> permute(int[] nums) {boolean[] used = new boolean[nums.length];dfs(nums, new ArrayList<>(), used);return result;}private void dfs(int[] nums, ArrayList<Integer> path, boolean[] used) {if (path.size() == nums.length) {result.add(new ArrayList<>(path));return;}for (int i = 0; i < nums.length; i++) {if (used[i]) {continue;}path.add(nums[i]);used[i] = true;dfs(nums, path, used);used[i] = false;path.remove(path.size() - 1);}}
这篇关于LeetCode 热题 HOT 100(P11~P20)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!