• 刷题篇--热题HOT 11-20


    20.有效的括号

     给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。有效字符串需满足:
     左括号必须用相同类型的右括号闭合。
     左括号必须以正确的顺序闭合。
    注意空字符串可被认为是有效字符串。
    输入: "()",输出: true;输入: "()[]{}",输出: true;输入: "(]",输出: false;输入: "([)]",输出: false;输入: "{[]}",输出: true;
    分析:第一反应是使用栈,遇到左括号压入,遇到右括号弹出并比较.

     1 class Solution {
     2     public boolean isValid(String s) {
     3         if(s.length()%2==1) return false;
     4         Stack<Character> brackets = new Stack();
     5         Map<Character,Character> map = new HashMap();
     6         map.put(')', '(');
     7         map.put(']', '[');
     8         map.put('}', '{');
     9         int countOfLeft=0,countOfRight=0;//计算左括号和右括号数量
    10         char[] chs = s.toCharArray();
    11         for(int i=0; i < chs.length; i++){
    12             if(chs[i] == '(' || chs[i] == '[' || chs[i] == '{'){
    13                 countOfLeft++;
    14                 brackets.push(chs[i]);
    15             }else if(chs[i] == ')' || chs[i] == ']' || chs[i] == '}'){
    16                 countOfRight++;
    17                 if(brackets.empty() || brackets.pop()!=map.get(chs[i])) return false;//如果括号栈为空或者弹出的不是正确左括号,则false
    18             }
    19         }
    20         return countOfLeft==countOfRight;//如果左括号数和右括号数量不相等则false
    21     }
    22 }

    21.合并两个有序链表

    将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
    示例:输入:1->2->4, 1->3->4.输出:1->1->2->3->4->4。

     分析:新建头节点(解决链表问题需要注意这个小Tips)

     1 /**
     2  * Definition for singly-linked list.
     3  * public class ListNode {
     4  *     int val;
     5  *     ListNode next;
     6  *     ListNode(int x) { val = x; }
     7  * }
     8  */
     9 class Solution {
    10     public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    11         ListNode preNode = new ListNode(-1);
    12         ListNode resNode = preNode;
    13         while(l1 != null && l2 != null){
    14             if(l1.val > l2.val){
    15                 preNode.next = l2;
    16                 l2 = l2.next;
    17             }else{
    18                 preNode.next = l1;
    19                 l1 = l1.next;
    20             }
    21             preNode = preNode.next;
    22         }
    23         preNode.next = l1==null ? l2 : l1;
    24         return resNode.next;
    25     }
    26 }

     22.括号生成

    给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

     例如,给出 n = 3,生成结果为:
    [
      "((()))",
      "(()())",
      "(())()",
      "()(())",
      "()()()"
    ]

    分析:n是括号的对数,即有n个左括号和n个右括号,当从前往后添加括号字符时,需要注意右括号的数量不能比左括号多,例如 ())…(因此第一个加入的肯定是左括号),并且左括号的数量不能超过n个。

      综上:添加括号字符时,只要保证左括号数目小于等于n,右括号数目小于等于左括号数目这两个条件就可以了。(从0开始计数)

     1 class Solution {
     2     List<String> list = new ArrayList();
     3     public List<String> generateParenthesis(int n) {
     4         String s = "";
     5         int numOfLeft = 0, numOfRight = 0;
     6         addParenthesis(n, s, numOfLeft, numOfRight);
     7         return list;
     8     }
     9     public void addParenthesis(int n, String s, int numOfLeft, int numOfRight){
    10         //递归结束条件
    11         if(s.length() == 2*n){
    12             list.add(s);
    13             return;
    14         }
    15         if(numOfLeft < n){
    16             addParenthesis(n, s+'(', numOfLeft+1, numOfRight);
    17         }
    18         if(numOfRight < numOfLeft){
    19             addParenthesis(n, s+')', numOfLeft, numOfRight+1);
    20         }
    21     }
    22 }

     23.合并K个排序列表

    合并 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

     输入: [   1->4->5,   1->3->4,   2->6 ] 输出: 1->1->2->3->4->4->5->6

    分析:21题我们做过合并两个链表,这里是合并K个链表,如何将两个联系起来?可以想到将第一个链表和第K个链表合并,第二个链表和第K-1个链表合并,一轮之后只剩下K/2个,再次执行此操作,直至K=1。时间复杂度:O(NK)

     1 /**
     2  * Definition for singly-linked list.
     3  * public class ListNode {
     4  *     int val;
     5  *     ListNode next;
     6  *     ListNode(int x) { val = x; }
     7  * }
     8  */
     9 class Solution {
    10     public ListNode mergeKLists(ListNode[] lists) {
    11         int k = lists.length;
    12         if(k == 0) return null;
    13         while(k > 1){
    14             for(int i=0; i < k/2; i++){
    15                 lists[i] = merge2Lists(lists[i], lists[k-1-i]);
    16             }
    17             k = (k+1)/2;
    18         }
    19         return lists[0];
    20     }
    21     public ListNode merge2Lists(ListNode l1, ListNode l2){
    22         ListNode newPre = new ListNode(-1);
    23         ListNode res = newPre;
    24         while(l1 != null && l2 != null){
    25             if(l1.val > l2.val){
    26                 newPre.next = l2;
    27                 l2 = l2.next;
    28             }else{
    29                 newPre.next = l1;
    30                 l1 = l1.next;
    31             }
    32             newPre = newPre.next;
    33         }
    34         newPre.next = l1 == null ? l2 : l1;
    35         return res.next;
    36     }
    37 }

    31.下一个排列

    实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。必须原地修改,只允许使用额外常数空间。
    以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
    1,2,3 → 1,3,2
    3,2,1 → 1,2,3
    1,1,5 → 1,5,1

    分析:下一个更大的排列:即从后往前找,看看后面有没有比前边一个数大的,如果有,则排列成最小数列。如果遍历完整个数组都没有找到符合条件的,则该数组是一个降序数组,此时应该考虑将数组转换成升序数组。

    nums = [1,5,6,4,7,6,5,3,1]  => 当判决到 4和7 时,发生改变,需要将4和4后面与4差值(比4大)最小的数5交换位置,即分成1,5,6,5和7,6,4,3,1两个部分,前一部分保证比之前数列值大,后一部分保证仅比之前数列大一点点(保证"下一个")。因此对后一部分还要进行升序排序。

     1 class Solution {
     2     public void nextPermutation(int[] nums) {
     3         int len = nums.length;
     4         //找更大排列分界点
     5         int i = len-1;
     6         while(i>0 && nums[i]<=nums[i-1]){////如果跳出循环,即nums[i-1]比nums[i]小,4<7,i-1即是4的索引
     7             i--;
     8         }
     9         i = i-1;//i为分界点,即4的索引,如果i=-1表示没有,即原数组是一个升序数组
    10         if(i>=0){
    11             int j = len-1;
    12             while(j > i && nums[j] <= nums[i]){
    13                 j--;//找出5的位置索引
    14             }
    15             swap(i,j,nums);//将4和5交换位置
    16         }
    17         //将4之后的数列降序处理或者整个数列进行降序处理
    18         //如果执行到此步说明没有更大数列,该数列为降序,现要改成升序
    19         reverse(nums, i+1);
    20     }
    21     public void reverse(int[] nums, int start){
    22         int i = start, j = nums.length-1;
    23         while(i<j){
    24             swap(i, j, nums);
    25             i++;
    26             j--;
    27         }
    28     }
    29     public void swap(int i, int j, int[] nums){
    30         int tmp = nums[i];
    31         nums[i] = nums[j];
    32         nums[j] = tmp;
    33     }
    34 }

    32.最长有效括号

    给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度。输入: "(()" 输出: 2 解释: 最长有效括号子串为 "()";输入: ")()())" 输出: 4 解释: 最长有效括号子串为 "()()"

    分析:使用栈,利用字符在字符串中的索引来计算最大长度。

    例如 :首先压入-1(便于计算最大长度),

        遇到‘(’就压入索引,

        遇到‘)’就弹出,

          如果弹出后栈不为空则当前最大长度为(当前索引-栈顶元素),并与总最大长度比较。

          如果弹出之后栈为空,说明之前的都弹完了,则压入当前索引,作为计算下一次最大长度初始值使用。

    =》

    =》

    =》

    =》

     1 class Solution {
     2     public int longestValidParentheses(String s) {
     3         Stack<Integer> stack = new Stack();
     4         stack.push(-1);
     5         int maxLen = 0;
     6         for(int i=0; i<s.length(); i++){
     7             if(s.charAt(i) == '('){
     8                 stack.push(i);
     9             }else{
    10                 stack.pop();
    11                 if(stack.empty()){
    12                     stack.push(i);
    13                 }else{
    14                     maxLen = Math.max(maxLen, i-stack.peek());
    15                 }
    16             }
    17         }
    18         return maxLen;
    19     }
    20 }

    33.搜索旋转排序数组

    假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。你可以假设数组中不存在重复的元素。你的算法时间复杂度必须是 O(log n) 级别。

    示例 1:输入: nums = [4,5,6,7,0,1,2], target = 0输出: 4
    示例 2:输入: nums = [4,5,6,7,0,1,2], target = 3输出: -1

    分析:由题目中给出的复杂度可以得知使用二分查找,不断缩小查找范围。通过半段有序部分来划分(也可以先二分查找分界点,然后通过分界点来划分,但是要进行两次二分查找,较为麻烦)。

      如果nums[start] <= nums[mid], 例如 2,3,4,5,6,7,0,1    2<5,则说明start~mid的元素是有序的

        如果nums[start] < target < nums[mid],就在start~mid这个范围找,反之,则在后半部分查找。

      如果nums[start] >= nums[mid], 例如 5,6,7,1,2,3,4,   5>1,则说明mid~end的元素是有序的

        如果nums[mid] < target < nums[end],就在mid~end这个范围找,反之,则在前半部分查找。

     1 class Solution {
     2     public int search(int[] nums, int target) {
     3         int start =0, end = nums.length-1;
     4         int mid;
     5         while(start <= end){
     6             mid = (start+end)/2;
     7             if(nums[mid]==target) return mid;
     8             if(nums[start] <= nums[mid]){
     9                 if(nums[start] <= target && target < nums[mid]){
    10                     end = mid - 1;
    11                 }else{
    12                     start = mid + 1;
    13                 }
    14             }else{
    15                 if(nums[mid] < target && target <= nums[end]){
    16                     start = mid +1;
    17                 }else{
    18                     end = mid-1;
    19                 }
    20             }
    21         }
    22         return -1;
    23     }
    24 }

    34.在排序数组中查找元素的第一个和最后一个位置

    给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。你的算法时间复杂度必须是 O(log n) 级别。如果数组中不存在目标值,返回 [-1, -1]。

    示例 1:输入: nums = [5,7,7,8,8,10], target = 8 输出: [3,4]

    示例 2:输入: nums = [5,7,7,8,8,10], target = 6 输出: [-1,-1]

    分析:初始值left = 0, right = len-1,mid = (left + right)/2。target与mid比较。因为本题不只是找一个,则需要分别查找左边索引和右边索引,编写两个函数。

     1 class Solution {
     2     public int[] searchRange(int[] nums, int target) {
     3         return new int[]{searchLeft(nums, target), searchRight(nums, target)};
     4     }
     5     //获取左边索引
     6     public int searchLeft(int[] nums, int target){
     7         int left = 0, right = nums.length-1, mid;
     8         while(left <= right){
     9             mid = (left + right)/2;
    10             if(nums[mid] == target){//当mid位置值等于目标值时
    11                 if(mid==0 || nums[mid-1] != target){//判断是否再向前进一步查询
    12                     return mid;
    13                 }else{
    14                     right = mid-1;//此时通过上面判断,mid-1位置依然等于target,因此进一步向左查询
    15                 }
    16             }else if(nums[mid] > target ){
    17                 right = mid - 1;
    18             }else{
    19                 left = mid + 1;
    20             }
    21         }
    22         return -1;
    23     }
    24     //获取右边索引
    25     public int searchRight(int[] nums, int target){
    26         int left = 0, right = nums.length-1, mid;
    27         while(left <= right){
    28             mid = (left + right)/2;
    29             if(nums[mid] == target){//当mid位置值等于目标值时
    30                 if(mid==nums.length-1 || nums[mid+1] != target){//判断是否再向前进一步查询
    31                     return mid;
    32                 }else{
    33                     left = mid+1;//此时通过上面判断,mid+1位置依然等于target,因此进一步向右查询
    34                 }
    35             }else if(nums[mid] > target ){
    36                 right = mid - 1;
    37             }else{
    38                 left = mid + 1;
    39             }
    40         }
    41         return -1;
    42     }
    43 }
     

    39.组合总和

    给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。所有数字(包括 target)都是正整数。解集不能包含重复的组合。

    示例 1:输入: candidates = [2,3,6,7], target = 7,所求解集为:[ [7], [2,2,3]]
    示例 2:输入: candidates = [2,3,5], target = 8,所求解集为:[  [2,2,2,2], [2,3,3], [3,5]]

    分析:排列组合,类似于剑指Offer里字符串排列

     1 class Solution {
     2     List<List<Integer>> res = new ArrayList();
     3     public List<List<Integer>> combinationSum(int[] candidates, int target) {
     4         Arrays.sort(candidates);//排序,方便去重
     5         List<Integer> list = new ArrayList();
     6         findCombination(candidates, target, 0, list);
     7         return res;
     8     }
     9     public void findCombination(int[] candidates, int target, int index, List<Integer> list){
    10         if(target < 0){
    11             return;
    12         } 
    13         if(target == 0){
    14             res.add(new ArrayList(list));
    15             return;
    16         }
    17         for(int i=index; i<candidates.length; i++){
    18             list.add(candidates[i]);
    19             findCombination(candidates, target-candidates[i], i, list);
    20             list.remove(list.size()-1);
    21         }
    22     }
    23 }

     42.接雨水

    给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

     

    上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。

    示例:输入: [0,1,0,2,1,0,1,3,2,1,2,1]输出: 6

    分析:当前柱子的接水量等于(min{左边最高柱子高度,右边最高柱子高度} - 当前高度),问题的关键就是如何找到并存储左右最高柱子高度值。

    方法一:暴力寻找,每次遍历i时,再次遍历i左边,寻找左边最高柱子,然后再次遍历右边,寻找右边最高柱子。循环嵌套,时间复杂度O(N^2) 空间复杂度 O(1)

    方法二:先从左往右遍历,使用数组存储左边最高值,再从右往左遍历,使用数组存储右边最高值。再从左往右遍历,计算接水量。时间复杂度O(N) 空间复杂度 O(N)。

     1 class Solution {
     2     public int trap(int[] height) {
     3         int len = height.length, res = 0;
     4         if(len == 0) return 0;
     5         //左边最高块
     6         int[] maxLeft = new int[len];
     7         maxLeft[0] = height[0];
     8         for(int i = 1; i < len; i++){
     9             maxLeft[i] = Math.max(maxLeft[i-1], height[i]);
    10         }
    11 
    12         //右边最高块
    13         int[] maxRight = new int[len];
    14         maxRight[len-1] = height[len-1];
    15         for(int i = len-2; i >= 0; i--){
    16             maxRight[i] = Math.max(maxRight[i+1], height[i]);
    17         }
    18 
    19         //计算res
    20         for(int i = 0; i < len; i++){
    21             res += Math.min(maxLeft[i], maxRight[i]) - height[i];
    22         }
    23 
    24         return res;
    25         
    26     }
    27 }

    方法三:双指针法,通过分析可以知道以最高块为分界线(i=7),左边(0~6)接水量由左边最高值决定,右边(7~11)接水量由右边最高值决定.

    那么如何在遍历中区分当前位置在左边还是右边呢?此时使用双指针,默认右边边界持有最高值,则当height[left] >= height[right]时,right指针就要向左移动,寻找比height[left]更大的值,反之,left指针向右移动,这个过程中双指针不断地向最高柱子移动,如果一个已经先到了,则会停止。时间复杂度O(N) 空间复杂度 O(1)

      具体步骤如下:

      while left < right

        if height[left] < height[right]

          if height[left] >= maxLeft

            maxLeft = height[left]   //更新左边最大值

          else

            res +=  maxLeft - height[left]  //计算结果

          left++

        else

          if height[right] >= maxright

            maxright = height[right]   //更新右边最大值

          else

            res +=  maxLeft - height[left]  //计算结果

          right --

     1 class Solution {
     2     public int trap(int[] height) {
     3         int left = 0, right = height.length-1, maxLeft = 0, maxRight = 0, res = 0;
     4         while(left < right){
     5             if(height[left] < height[right]){
     6                 if(height[left] >= maxLeft){
     7                     maxLeft = height[left];
     8                 }else{
     9                     res += maxLeft - height[left];
    10                 }
    11                 left++;
    12             }else{
    13                 if(height[right] >= maxRight){
    14                     maxRight = height[right];
    15                 }else{
    16                     res += maxRight - height[right];
    17                 }
    18                 right--;
    19             } 
    20         }
    21         return res;
    22     }
    23 }
  • 相关阅读:
    HDU 5714
    C++ 中的名称冲突之 "y1"
    FFT 模板
    Modular Query
    找礼物(find)(模拟)
    水流(water)(BFS)(DFS)
    单词接龙(dragon)(BFS)
    细菌(disease) (位运算)(状态压缩)
    Diamond Collector (动态规划)
    超级素数(sprime) (BFS)
  • 原文地址:https://www.cnblogs.com/qmillet/p/12111782.html
Copyright © 2020-2023  润新知