热题HOT 100(11-20)

11.给定两个二进制字符串,返回他们的和(用二进制表示)。
输入为非空字符串且只包含数字 1 和 0。

整体思路是将两个字符串较短的用 0 补齐,使得两个字符串长度一致,然后从末尾进行遍历计算,得到最终结果。
但由于字符串操作原因,不确定最后的结果是否会多出一位进位,所以会有 2 种处理方式:
第一种,在进行计算时直接拼接字符串,会得到一个反向字符,需要最后再进行翻转
第二种,按照位置给结果字符赋值,最后如果有进位,则在前方进行字符串拼接添加进位
时间复杂度:O(n)

package leetcode;
public class AddBinary {
    public static String addBinary(String a, String b) {
        StringBuilder result = new StringBuilder();
        int ca=0;
        for(int i=a.length()-1,j=b.length()-1;i>=0||j>=0;i--,j--) {
            int sum=0;
            sum+=(i>=0)?a.charAt(i)-'0':0;
            sum+=(j>=0)?b.charAt(j)-'0':0;
            sum+=ca;
            result.append(sum%2);
            ca=sum/2;
        }
        result.append(ca==1?ca:"");
        return result.reverse().toString();
    }
    public static void main(String[] args) {
        System.out.println(addBinary("101","110"));
    }
}

12.给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。


示例

输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

排列组合
从根节点到空节点一共有多少条路径


package wg;
import java.util.ArrayList;
import java.util.List;
//这里不用StringBuffer是因为它不产生新的未使用对象
public class Solution {
    private static String[] letterMap = {
            " ",    //0
            "",     //1
            "abc",  //2
            "def",  //3
            "ghi",  //4
            "jkl",  //5
            "mno",  //6
            "pqrs", //7
            "tuv",  //8
            "wxyz"  //9
    };
    private static ArrayList res;//用来装递归过程中找到的结果
    //得到结果集
    public static List letterCombinations(String digits) {
        res = new ArrayList();
        if(digits.equals(""))
            return res;
        findCombination(digits, 0, "");
        return res;
    }
    //递归函数 处理得字符串 字符的位置 得到的结果s
    private static void findCombination(String digits, int index, String s){
        if(index == digits.length()){
            res.add(s);
            return;
        }
        Character c = digits.charAt(index);
        String letters = letterMap[c - '0'];
        for(int i = 0 ; i < letters.length() ; i ++){
            findCombination(digits, index+1, s+letters.charAt(i));

        }
        return;
    }
    public static void main(String[] args) {
        List results = new ArrayList<>();
        results = letterCombinations("235");
        for(String s : results)
            System.out.println(s);
    }
}
/*
adj
adk
adl
aej
aek
ael
afj
afk
afl
bdj
bdk
bdl
bej
bek
bel
bfj
bfk
bfl
cdj
cdk
cdl
cej
cek
cel
cfj
cfk
cfl
*/

13.给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

整体思路是让前面的指针先移动n步,之后前后指针共同移动直到前面的指针到尾部为止
1.设预先指针 pre 的下一个节点指向 head,设前指针为 start,后指针为 end,二者都等于 pre
2.start 先向前移动n步
3.之后 start 和 end 共同向前移动,此时二者的距离为 n,当 start 到尾部最后一个节点时(start.next != null),end 的下一个节点位置恰好为倒数第 n 个节点
4.循环结束条件为 start.next != null
5.删除后返回 pre.next,为什么不直接返回 head 呢,因为 head 有可能是被删掉的点

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {    
        ListNode pre = new ListNode(0);
        pre.next = head;
        ListNode start = pre, end = pre;
        while(n != 0) {
            start = start.next;
            n--;
        }
        while(start.next != null) {
            start = start.next;
            end = end.next;
        }
        end.next = end.next.next;
        return pre.next;
    }
}

14.给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 注意空字符串可被认为是有效字符串。

栈先入后出特点恰好与本题括号排序特点一致,即若遇到左括号入栈,遇到右括号时将对应栈顶左括号出栈,则遍历完所有括号后 stack 仍然为空;
1.可以增加一个判断:如果栈的深度大于字符串长度的1/2,就返回false。因为当出现这种情况的时候,即使后面的全部匹配,栈也不会为空。
2.如果字符串长度是奇数,直接返回false。

package wg;
import java.util.HashMap;
import java.util.Stack;
public class Solution {
    private HashMap mappings;
    public Solution() {
        this.mappings = new HashMap<>();
        //把结束符号用来判断
        this.mappings.put(')','(');
        this.mappings.put(']','[');
        this.mappings.put('}','{');
    }
    public boolean isValid(String s){
        if(s.length()%2==1)
            return false;
        Stack stack = new Stack<>();
        for(int i=0,j=s.length();ij/2){
                if(stack.size()>j/2)return false;
            }
        }
        return stack.isEmpty();
    }
    public static void main(String[] args) {
        Solution solution = new Solution();
        System.out.println(solution.isValid("{}(())[[]]"));
    }
}

15.将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的

示例
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
递归实现
新链表也不需要构造新节点,我们下面列举递归三个要素
1.终止条件:两条链表分别名为l1l2,当l1为空或l2为空时结束
2.返回值:每一层调用都返回排序好的链表头
3.本级递归内容:如果l1val 值更小,则将 l1.next与排序好的链表头相接,l2同理
复杂度:O(m+n)ml1的长度,nl2的长度

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
         if(l1 == null) {
            return l2;
        }
        if(l2 == null) {
            return l1;
        }
//l1的下一个节点连接谁?
        if(l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {//l2的下一个节点连接谁?
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }

16.给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合
例如,给出 n = 3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

什么是动态规划?在此题中,动态规划的思想类似于数学归纳法,当知道所有i的情况时,我们可以通过某种算法算出 i=n 的情况。
本题最核心的思想是,考虑i=n 时相比 n-1 组括号增加的那一组括号的位置。

思路:
当我们清楚所有i时括号的可能生成排列后,对与i=n的情况,我们考虑整个括号排列中最左边的括号。
它一定是一个左括号,那么它可以和它对应的右括号组成一组完整的括号 "( )",我们认为这一组是相比n-1增加进来的括号。剩下的括号要么在这一组新增的括号内部,要么在这一组新增括号的外部(右侧)。
所以:
剩下的N-1个括号分为两部分,P个括号和Q个括号,P+Q=N-1,然后这两部分分别处于新增的括号和新增的括号右边,各自进行括号的排列组合。

package wg;
import java.util.LinkedList;
import java.util.List;
public class Solution {
    public static List generateParenthesis(int n){
        LinkedList> results = new LinkedList>();
        if(n==0)
            return results.get(0);
        LinkedList list0 = new LinkedList<>();
        list0.add("");
        results.add(list0);//results.get(0)的结果;
        LinkedList list1 = new LinkedList<>();
        list1.add("()");
        results.add(list1);//results.get(1)的结果;
        //分别得到results.get(i)的结果
        for(int i=2;i<=n;i++){
            LinkedList temp = new LinkedList<>();
            //p+q=i-1  这里遍历p
            for(int j=0;j str1 = results.get(j);
                LinkedList str2 = results.get(i-1-j);
                for(String s1:str1){
                    for(String s2:str2){
                        String el = "("+s1+")"+s2;//p位于内部,q位于右边
                        temp.add(el);
                    }
                }
            }
            results.add(temp);
        }
        return results.get(n);
    }
    public static void main(String[] args) {
        List re = generateParenthesis(4);
        System.out.println(re);
    }
}
[()()()(), ()()(()), ()(())(), ()(()()), ()((())), (())()(), (())(()), (()())(), ((()))(), (()()()), (()(())), ((())()), ((()())), (((())))]

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

1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

我们需要从右边找到第一对两个连续的数字 a[i]a[i−1],它们满足 a[i]>a[i-1]。现在,没有对 a[i-1]右侧的重新排列可以创建更大的排列,因为该子数组由数字按降序组成。因此,我们需要重新排列a[i−1]右边的数字,包括它自己。
现在,什么样子的重新排列将产生下一个更大的数字呢?我们想要创建比当前更大的排列。因此,我们需要将数字 a[i−1] 替换为位于其右侧区域的数字中比它稍大(就是大于但是这个是大于中的最小)的数字,如 下图的a[j]


我们交换数字 a[i−1]a[j]。我们现在在索引i−1 处有正确的数字。 但目前的排列仍然不是我们正在寻找的排列。我们需要通过仅使用 a[i−1]右边的数字来形成最小的排列。 因此,我们需要重新放置右边的数字以获得最小的排列。右边的所有数字都已按降序排序,我们只需要反转 a[i−1]之后的数字,以获得下一个最小的字典排列。
下面的动画将有助于你理解:

package wg;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class Solution {
    private static void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    private static void reverse(int[] nums, int start) {
        int i = start, j = nums.length - 1;
        while (i < j) {
            swap(nums, i, j);
            i++;
            j--;
        }
    }
    public static void nextPermutation(int[] nums) {
        int i = nums.length - 2;
        //从倒是第2个开始找 当nums[i+1]>nums[i]退出
        while (i >= 0 && nums[i + 1] <= nums[i]) {
            i--;
        }
        //i<0表示整个是降序排列,反转整个数组满足题目条件
        if (i >= 0) {
            int j = nums.length - 1;
            //找打稍大的那个数然后交换位置
            while (j >= 0 && nums[j] <= nums[i]) {
                j--;
            }
            swap(nums, i, j);
        }
        //这个反转开始的地方是i的下一个位置到最后
        reverse(nums, i + 1);
    }
    public static void main(String[] args) {
        int[] nums = {3,2,4,1};
        nextPermutation(nums);
        System.out.println(Arrays.toString(nums));
//[3, 4, 1, 2]
    }
}

18.给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度

输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"

我们利用两个计数器leftright。首先,我们从左到右遍历字符串,对于遇到的每个 (,我们增加left 计算器,对于遇到的每个) ,我们增加right 计数器。每当 left计数器与right计数器相等时,我们计算当前有效字符串的长度,并且记录目前为止找到的最长子字符串。如果 right 计数器比 left计数器大时,我们将leftright计数器同时变回 0 。
接下来,我们从右到左做一遍类似的工作

 public int longestValidParentheses(String s) {
        int left = 0, right = 0, maxlength = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                left++;
            } else {
                right++;
            }
            if (left == right) {
                maxlength = Math.max(maxlength, 2 * right);
            } else if (right >= left) {
                left = right = 0;
            }
        }
        left = right = 0;
        for (int i = s.length() - 1; i >= 0; i--) {
            if (s.charAt(i) == '(') {
                left++;
            } else {
                right++;
            }
            if (left == right) {
                maxlength = Math.max(maxlength, 2 * left);
            } else if (left >= right) {
                left = right = 0;
            }
        }
        return maxlength;
    }

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

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

二分查找算法

package wg;
import java.util.Arrays;
public class Solution {
    public int[] searchRange(int[] nums, int target) {
        int len = nums.length;
        // 特判,这一步很重要,否则执行到后序方法可能会出现数组下标越界
        // 同时后序两个方法也不用做特殊判断了
        if (len == 0) {
            return new int[]{-1, -1};
        }
        int num1 = findLowerBound(nums, target);
        // 细节:如果左边界都搜索不到,右边界也没有必要看了
        if (num1 == -1) {
            return new int[]{-1, -1};
        }
        int num2 = findUpBound(nums, target);
        return new int[]{num1, num2};
    }
    private int findLowerBound(int[] nums, int target) {
        int len = nums.length;
        int left = 0;
        int right = len;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                right = mid;
            } else if(nums[mid] < target){
                left = mid + 1;
            }else{
                right = mid;
            }
        }
        // 因为有可能不存在目标元素,最后一定要单独判断一下
        if(left==nums.length)return -1;
        return nums[left] == target ? left : -1;
    }
    private int findUpBound(int[] nums, int target) {
        // 找等于 target 的最后 1 个数的索引,大于一定不符合要求
        // 因为有可能不存在,最后一定要单独判断一下
        int len = nums.length;
        int left = 0;
        int right = len;
        while (left < right) {
             int mid = left + (right - left ) / 2;
            if (nums[mid] > target) {
                right = mid;
            } else if(nums[mid] == target){
                left = mid+1;
            }else{
                left = mid+1;
            }
        }
        // 因为有可能不存在目标元素,最后一定要单独判断一下
        if (left == 0) return -1;
        return nums[left-1] == target ? (left-1) : -1;
    }
    public static void main(String[] args) {
        int[] nums = {5, 7, 7, 8, 8, 10};
        int target = 8;
        Solution solution4 = new Solution();
        int[] res = solution4.searchRange(nums, target);
        System.out.println(Arrays.toString(res));
    }
}

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

输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]

回溯算法 + 剪枝
思路:以target = 7 为根结点,每一个分支做减法。减到 0或者负数的时候,剪枝。其中,减到 0 的时候结算,这里 “结算” 的意思是添加到结果集。


这张图画出的结果有 40,对应的路径是 [[2, 2, 3], [2, 3, 2], [3, 2, 2], [7]],而示例中的解集只有[[7], [2, 2, 3]],很显然,结果集出现了重复。重复的原因是
后面分支的更深层的边出现了前面分支低层的边的值。
分支上的数字指箭头上面的数字

这个问题也不难解决,把候选数组排个序就好了,后面选取的数不能比前面选的数还要小

package wg;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
public class Solution {
    private List> res = new ArrayList<>();
    private int[] candidates;
    private int len;
    public List> combinationSum(int[] candidates, int target) {
        int len = candidates.length;
        if (len == 0) {
            return res;
        }
        // 优化添加的代码1:先对数组排序,可以提前终止判断
        Arrays.sort(candidates);
        this.len = len;
        this.candidates = candidates;
        findCombinationSum(target, 0, new Stack<>());
        return res;
    }
    //第一个参数 此时剩余组合的值 查找到了数组的第几个数字 保存结果的栈
    private void findCombinationSum(int residue, int start, Stack pre) {
        if (residue == 0) {
            // Java 中可变对象是引用传递,因此需要将当前 path 里的值拷贝出来
            //把栈变成ArrayList 利用栈变成构造ArrayList
            res.add(new ArrayList<>(pre));
            return;
        }
        // 优化添加的代码2:在循环的时候做判断,尽量避免系统栈的深度
        // residue - candidates[i] 表示下一轮的剩余,如果下一轮的剩余都小于 0 ,就没有必要进行后面的循环了
        // 这一点基于原始数组是排序数组的前提,因为如果计算后面的剩余,只会越来越小
        for (int i = start; i < len && residue - candidates[i] >= 0; i++) {
            pre.add(candidates[i]);
            // 【关键】因为元素可以重复使用,这里递归传递下去的是 i 而不是 i + 1
            findCombinationSum(residue - candidates[i], i, pre);
            pre.pop();
        }
    }
    public static void main(String[] args) {
        int[] candidates = {2, 3, 6, 7};
        int target = 7;
        Solution solution = new Solution();
        List> combinationSum = solution.combinationSum(candidates, target);
        System.out.println(combinationSum);
    }
}

文章参考
https://leetcode-cn.com/problemset/hot-100/

你可能感兴趣的:(热题HOT 100(11-20))