Leetcode1--50题目思路简要整理

下面只是对LeetCode1–100题最优思路和核心代码的简要整理,以应对面试中的手撕代码环节。对于一般计算机专业出身的人来说,对于一个算法题目给定一个思路实现起来应该问题不大,关键在于找到这个思路。根据自己的面试经历来看,面试中的题目描述不会很复杂,一般情况下只要分析出思路很快就可以写出来。且大多数面试官喜欢在LeetCode中找一些小而简练的题目,前100道题目出现的频率很大。
先把所有的考点思想弄懂,然后分析一些经典的题目应对手撕代码环节问题不大。 考点主要包括:二分及其变形、快排、归并、求众数、dp,bfs,dfs,数组合并,链表倒序,是否有环,2链表是否相交,链表排序,最长子序列,最长回文子串,背包等dp问题,二叉树的各种遍历,递归非递归,求深度,判断平衡树,最近公共祖先,topk,第k大,全排列;

1 两数之和

简要描述:
给定一个整数数组和一个target值,求数组中两个元素下标使得 a[i] + a[j] = target。
思路:
(1)采用hashmap。逐个扫描数组元素,对当前元素判断target-a[i]是否在hashmap 中,如果在直接返回,否则将当前元素加入。
(2)排序+双指针。先对数组排序,然后左右指针分别指向最小值和最大值,根据两个指针所指值的和与target比较来移动指针,如果和大于target则右指针往左移动,否则左指针往右移动。

2 两数相加

简要描述:
两个数用链表形式存储,如342,存储为2->4->3,求两个数的和。
思路;
(1)两个指针扫描两个链表,并定义一个新链表存储结果。期间需要注意两个链表的长度可能不同,且最高位存在进位问题。

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode p1 = l1;
        ListNode p2 = l2;
        ListNode head = new ListNode(0);
        ListNode cur = head;
        int carry = 0;
        while(p1 != null || p2 != null){
            int x = (p1 != null) ? p1.val : 0;
            int y = (p2 != null) ? p2.val : 0;
            int sum = carry + x + y;
            carry = sum / 10;
            cur.next = new ListNode(sum % 10);
            cur = cur.next;
            if(p1 != null){
                p1 = p1.next;
            }
            if(p2 != null){
                p2 = p2.next;
            }
        }
        if(carry > 0){
            cur.next = new ListNode(carry);
        }
        return head.next;
    }

3 无重复字符最长串

描述:
给定一个字符串,寻找其子串,使得1该子串中不存在重复字符;2该子串的长度最长。
思路:
(1)使用hashset+滑动窗口。对于[i,j)所表示的子串,如果a[j]与区间无重复则j向右滑动。如果重复则找出重复的字符j’在字符串中的位置,将i变成j’+1.

public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        Map<Character, Integer> map = new HashMap<>(); // current index of character
        // try to extend the range [i, j]
        for (int j = 0, i = 0; j < n; j++) {
            if (map.containsKey(s.charAt(j))) {
                i = Math.max(map.get(s.charAt(j)), i);
            }
            ans = Math.max(ans, j - i + 1);
            map.put(s.charAt(j), j + 1);
        }
        return ans;
    }

public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        int[] index = new int[128]; // current index of character
        // try to extend the range [i, j]
        for (int j = 0, i = 0; j < n; j++) {
            i = Math.max(index[s.charAt(j)], i);
            ans = Math.max(ans, j - i + 1);
            index[s.charAt(j)] = j + 1;
        }
        return ans;
    }

4 寻找两个有序数组中位数

描述:
给定两个有序数组,求两个有序数组合并后的中位数。
思路:
(1)两个数组合并,返回中间位置数。复杂度为O(n),因为连个数组都有序,所以排序复杂度为O(n);
(2)二分搜索,在较短的数组中寻找分界点。
解题思路详细描述

public double findMedianSortedArrays(int[] A, int[] B) {
        int m = A.length;
        int n = B.length;
        if (m > n) { // to ensure m<=n
            int[] temp = A; A = B; B = temp;
            int tmp = m; m = n; n = tmp;
        }
        int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
        while (iMin <= iMax) {
            int i = (iMin + iMax) / 2;
            int j = halfLen - i;
            if (i < iMax && B[j-1] > A[i]){
                iMin = i + 1; // i is too small
            }
            else if (i > iMin && A[i-1] > B[j]) {
                iMax = i - 1; // i is too big
            }
            else { // i is perfect
                int maxLeft = 0;
                if (i == 0) { maxLeft = B[j-1]; }
                else if (j == 0) { maxLeft = A[i-1]; }
                else { maxLeft = Math.max(A[i-1], B[j-1]); }
                if ( (m + n) % 2 == 1 ) { return maxLeft; }

                int minRight = 0;
                if (i == m) { minRight = B[j]; }
                else if (j == n) { minRight = A[i]; }
                else { minRight = Math.min(B[j], A[i]); }

                return (maxLeft + minRight) / 2.0;
            }
        }
        return 0.0;
    }

5 寻找字符串最长回文子串

描述:
给定一个字符串,求最长回文子串。
思路:
解题思路参考
(1)转化为求解最长公共子串问题,但是需要注意判断一些特殊情况,例如:abacfgcaba;需要增加下标的判断。
(2)P(i,j)=(P(i+1,j−1)&&S[i]==S[j]),先初始化长度为1的回文串然后初始化长度为2的回文串,然后利用上面的状态转移方程。

public String longestPalindrome(String s) {
    int length = s.length();
    boolean[][] P = new boolean[length][length];
    int maxLen = 0;
    String maxPal = "";
    for (int len = 1; len <= length; len++) //遍历所有的长度
        for (int start = 0; start < length; start++) {
            int end = start + len - 1;
            if (end >= length) //下标已经越界,结束本次循环
                break;
            P[start][end] = (len == 1 || len == 2 || P[start + 1][end - 1]) && s.charAt(start) == s.charAt(end); //长度为 1 和 2 的单独判断下
            if (P[start][end] && len > maxLen) {
                maxPal = s.substring(start, end + 1);
            }
        }
    return maxPal;
}


//进行空间上的优化
public String longestPalindrome7(String s) {
		int n = s.length();
		String res = "";
		boolean[] P = new boolean[n];
		for (int i = n - 1; i >= 0; i--) {
			for (int j = n - 1; j >= i; j--) {
				P[j] = s.charAt(i) == s.charAt(j) && (j - i < 3 || P[j - 1]);
				if (P[j] && j - i + 1 > res.length()) {
					res = s.substring(i, j + 1);
				}
			}
		}
		return res;
	}

(3)中心扩展法,一共有2n-1个中心,不断扩展。

public String longestPalindrome(String s) {
    if (s == null || s.length() < 1) return "";
    int start = 0, end = 0;
    for (int i = 0; i < s.length(); i++) {
        int len1 = expandAroundCenter(s, i, i);
        int len2 = expandAroundCenter(s, i, i + 1);
        int len = Math.max(len1, len2);
        if (len > end - start) {
            start = i - (len - 1) / 2;
            end = i + len / 2;
        }
    }
    return s.substring(start, end + 1);
}

private int expandAroundCenter(String s, int left, int right) {
    int L = left, R = right;
    while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {
        L--;
        R++;
    }
    return R - L - 1;
}

6 z字形变换

描述:
给定一个字符串按照一定规则排列。
思路:
可以按照行或者按照列方式排序,面试中这类题目一般很少出现。

public String convert(String s, int numRows) {

        if (numRows == 1) return s;

        List<StringBuilder> rows = new ArrayList<>();
        for (int i = 0; i < Math.min(numRows, s.length()); i++)
            rows.add(new StringBuilder());

        int curRow = 0;
        boolean goingDown = false;

        for (char c : s.toCharArray()) {
            rows.get(curRow).append(c);
            if (curRow == 0 || curRow == numRows - 1) goingDown = !goingDown;
            curRow += goingDown ? 1 : -1;
        }

        StringBuilder ret = new StringBuilder();
        for (StringBuilder row : rows) ret.append(row);
        return ret.toString();
    }

7 整数反转

描述:
给定一个32位有符号数字,进行翻转,返回一个整数。例如:-123 -> -321
思路:
(1)采用字符串方法做,先转化为字符串,然后翻转,然后再转化为整数;
(2)直接通过除法和取模运算进行实现,每次取最后一位。

public int reverse(int x) {
        int rev = 0;
        while (x != 0) {
            int pop = x % 10;
            x /= 10;
            if (rev > Integer.MAX_VALUE/10 || (rev == Integer.MAX_VALUE / 10 && pop > 7)) return 0;
            if (rev < Integer.MIN_VALUE/10 || (rev == Integer.MIN_VALUE / 10 && pop < -8)) return 0;
            rev = rev * 10 + pop;
        }
        return rev;
    }

8 字符串转整数

描述:
实现一个atoi函数,将字符串转换为整数。
思路:
进行不同情况的讨论,或者采用正则表达式解决,一般面试不会考察这类题目。

9 回文数

描述:
判断一个整数是否是回文数。
思路:
(1)转化为字符串判断,但是会有额外的开销;
(2)反转一半数字,我们将原始数字除以 10,然后给反转后的数字乘上 10,所以,当原始数字小于反转后的数字时,就意味着我们已经处理了一半位数的数字。

 public bool IsPalindrome(int x) {
        // 特殊情况:
        // 如上所述,当 x < 0 时,x 不是回文数。
        // 同样地,如果数字的最后一位是 0,为了使该数字为回文,
        // 则其第一位数字也应该是 0
        // 只有 0 满足这一属性
        if(x < 0 || (x % 10 == 0 && x != 0)) {
            return false;
        }

        int revertedNumber = 0;
        while(x > revertedNumber) {
            revertedNumber = revertedNumber * 10 + x % 10;
            x /= 10;
        }

        // 当数字长度为奇数时,我们可以通过 revertedNumber/10 去除处于中位的数字。
        // 例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,revertedNumber = 123,
        // 由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。
        return x == revertedNumber || x == revertedNumber/10;
    }

10 正则表达式匹配

描述:
给定一个字符串和一个模式串,看是否可以匹配,其中’.’ 匹配任意单个字符,’*’ 匹配零个或多个前面的那一个元素。
思路:
因为题目拥有 最优子结构 ,一个自然的想法是将中间结果保存起来。我们通过用 dp(i,j) 表示 text[i:] 和 pattern[j:] 是否能匹配。

public boolean isMatch(String text, String pattern) {
        boolean[][] dp = new boolean[text.length() + 1][pattern.length() + 1];
        dp[text.length()][pattern.length()] = true;

        for (int i = text.length(); i >= 0; i--){
            for (int j = pattern.length() - 1; j >= 0; j--){
                boolean first_match = (i < text.length() &&
                                       (pattern.charAt(j) == text.charAt(i) ||
                                        pattern.charAt(j) == '.'));
                if (j + 1 < pattern.length() && pattern.charAt(j+1) == '*'){
                    dp[i][j] = dp[i][j+2] || first_match && dp[i+1][j];
                } else {
                    dp[i][j] = first_match && dp[i+1][j+1];
                }
            }
        }
        return dp[0][0];
    }

11 盛最多水的容器

描述:给定一个非负整数数组,代表隔板的高度,寻找两个隔板使得期间可以存储的水面积最大(max(a[i],a[j])*(j-i))max;
思路:
(1)双指针法。左右两个指针,每次移动两个指针中较小的指针。如果我们试图将指向较长线段的指针向内侧移动,矩形区域的面积将受限于较短的线段而不会获得任何增加。但是,在同样的条件下,移动指向较短线段的指针尽管造成了矩形宽度的减小,但却可能会有助于面积的增大。因为移动较短线段的指针会得到一条相对较长的线段,这可以克服由宽度减小而引起的面积减小。

public int maxArea(int[] height) {
        int maxarea = 0, l = 0, r = height.length - 1;
        while (l < r) {
            maxarea = Math.max(maxarea, Math.min(height[l], height[r]) * (r - l));
            if (height[l] < height[r])
                l++;
            else
                r--;
        }
        return maxarea;
    }

12 整数转罗马数字

描述:如题
思路:贪心算法,按照规则进行贪心转化即可。

public String intToRoman(int num) {
        // 把阿拉伯数字与罗马数字可能出现的所有情况和对应关系,放在两个数组中
        // 并且按照阿拉伯数字的大小降序排列,这是贪心选择思想
        int[] nums = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
        String[] romans = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};

        StringBuilder stringBuilder = new StringBuilder();
        int index = 0;
        while (index < 13) {
            // 特别注意:这里是等号
            while (num >= nums[index]) {
                // 注意:这里是等于号,表示尽量使用大的"面值"
                stringBuilder.append(romans[index] + " ");
                num -= nums[index];
            }
            index++;
        }
        return stringBuilder.toString();
    }

13 罗马数字转整数

描述:如题
思路:
1构建一个字典记录所有罗马数字子串,注意长度为2的子串记录的值是(实际值 - 子串内左边罗马数字代表的数值)
2这样一来,遍历整个 ss 的时候判断当前位置和前一个位置的两个字符组成的字符串是否在字典内,如果在就记录值,不在就说明当前位置不存在小数字在前面的情况,直接记录当前位置字符对应值

class Solution {
public:
    int romanToInt(string s) {
        unordered_map m = {{"I", 1}, {"IV", 3}, {"IX", 8}, {"V", 5}, {"X", 10}, {"XL", 30}, {"XC", 80}, {"L", 50}, {"C", 100}, {"CD", 300}, {"CM", 800}, {"D", 500}, {"M", 1000}};
        int r = m[s.substr(0, 1)];
        for(int i=1; i

14 最长公共前缀

描述:给定一个字符串数组,求最长公共前缀
思路:
(1)二分查找,查找范围[0,minLen],切分条件,如果[0,mid] 不是前缀则丢弃后半个区间,否则丢弃前半个区间;
(2)分治思想,每次将数组折半进行求解;
(3)水平扫描,针对最短的串的每一位进行比较;

15 三数之和

思路:双指针,遍历数组每一个元素,然后按照两数和的双指针方法求解。

16 最接近的三数之和

思路:排序+双指针,判断条件稍加更改

class Solution {
    public int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
        int ans = nums[0] + nums[1] + nums[2];
        for(int i=0;i<nums.length;i++) {
            int start = i+1, end = nums.length - 1;
            while(start < end) {
                int sum = nums[start] + nums[end] + nums[i];
                if(Math.abs(target - sum) < Math.abs(target - ans))
                    ans = sum;
                if(sum > target)
                    end--;
                else if(sum < target)
                    start++;
                else
                    return ans;
            }
        }
        return ans;
    }
}

17 电话号码的字母组合

思路:递归解决

class Solution {
  Map<String, String> phone = new HashMap<String, String>() {{
    put("2", "abc");
    put("3", "def");
    put("4", "ghi");
    put("5", "jkl");
    put("6", "mno");
    put("7", "pqrs");
    put("8", "tuv");
    put("9", "wxyz");
  }};

  List<String> output = new ArrayList<String>();

  public void backtrack(String combination, String next_digits) {
    // if there is no more digits to check
    if (next_digits.length() == 0) {
      // the combination is done
      output.add(combination);
    }
    // if there are still digits to check
    else {
      // iterate over all letters which map 
      // the next available digit
      String digit = next_digits.substring(0, 1);
      String letters = phone.get(digit);
      for (int i = 0; i < letters.length(); i++) {
        String letter = phone.get(digit).substring(i, i + 1);
        // append the current letter to the combination
        // and proceed to the next digits
        backtrack(combination + letter, next_digits.substring(1));
      }
    }
  }

  public List<String> letterCombinations(String digits) {
    if (digits.length() != 0)
      backtrack("", digits);
    return output;
  }
}

18 四数之和

思路:两个2sum,先遍历找到所有的两个数的组合,然后从生下的数里面找出符合条件的两个数。

19 删除链表的倒数第N个节点

描述:如题
思路:注意下标的大小和指针的位置
(1)两次遍历,删除L-N+1位置的节点;
(2)一个指针先走n+1步,然后两个指针同时走;

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode first = dummy;
    ListNode second = dummy;
    // Advances first pointer so that the gap between first and second is n nodes apart
    for (int i = 1; i <= n + 1; i++) {
        first = first.next;
    }
    // Move first to the end, maintaining the gap
    while (first != null) {
        first = first.next;
        second = second.next;
    }
    second.next = second.next.next;
    return dummy.next;
}

20 有效括号

描述:给定一个括号序列判断其是否有效。
思路:
(1)栈,遇到左括号直接入栈,如果遇到右括号则与栈顶元素匹配,如果匹配则继续处理,否则不是有效序列;最后如果栈不为空也不是有效序列。

class Solution {
    public boolean isValid(String s){
        Stack<Character> stack = new Stack<>();
        char[] chars = s.toCharArray();
        for(char aChar : chars){
            if(aChar == '(' || aChar == '{' || aChar == '['){
                stack.push(aChar);
            }else if(stack.size() == 0 || !isSym(stack.pop(), aChar)){
                return false;
            }
        }
        return stack.size()==0;
    }

    private boolean isSym(char c1, char c2) {
        return (c1 == '(' && c2 == ')') || (c1 == '[' && c2 == ']') || (c1 == '{' && c2 == '}');
    }
}

21 合并两个有序链表

描述:如题
思路:两个指针遍历两个链表,注意两个链表的长度不同的情况。

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null){
            return l2;
        }
        if(l2 == null){
            return l1;
        }
        ListNode p1 = l1;
        ListNode p2 = l2;
        ListNode head = new ListNode(0);
        ListNode cur = head;
        while(p1 != null && p2 != null){
            if(p1.val < p2.val){
                cur.next = new ListNode(p1.val);
                p1 = p1.next;
            }else{
                cur.next = new ListNode(p2.val);
                p2 = p2.next;
            }
            cur = cur.next;
        }
        if(p1 != null){
            cur.next = p1;
        }
        if(p2 != null){
            cur.next = p2;
        }
        return head.next;
    }

22 括号生成

描述:给定一个N,求出N对括号的所有合法序列
思路:采用回溯法,只要括号数量小于N就可以一直添加。

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> ans = new ArrayList();
        backtrack(ans, "", 0, 0, n);
        return ans;
    }

    public void backtrack(List<String> ans, String cur, int open, int close, int max){
        if (cur.length() == max * 2) {
            ans.add(cur);
            return;
        }

        if (open < max)
            backtrack(ans, cur+"(", open+1, close, max);
        if (close < open)
            backtrack(ans, cur+")", open, close+1, max);
    }
}

23 合并K个排序链表

思路:(1)优先队列。采用优先队列进行比较。

from Queue import PriorityQueue

class Solution(object):
    def mergeKLists(self, lists):
        """
        :type lists: List[ListNode]
        :rtype: ListNode
        """
        head = point = ListNode(0)
        q = PriorityQueue()
        for l in lists:
            if l:
                q.put((l.val, l))
        while not q.empty():
            val, node = q.get()
            point.next = ListNode(val)
            point = point.next
            node = node.next
            if node:
                q.put((node.val, node))
        return head.next

(2)采用分治法,两个两个一组进行合并

class Solution(object):
    def mergeKLists(self, lists):
        """
        :type lists: List[ListNode]
        :rtype: ListNode
        """
        amount = len(lists)
        interval = 1
        while interval < amount:
            for i in range(0, amount - interval, interval * 2):
                lists[i] = self.merge2Lists(lists[i], lists[i + interval])
            interval *= 2
        return lists[0] if amount > 0 else lists

    def merge2Lists(self, l1, l2):
        head = point = ListNode(0)
        while l1 and l2:
            if l1.val <= l2.val:
                point.next = l1
                l1 = l1.next
            else:
                point.next = l2
                l2 = l1
                l1 = point.next.next
            point = point.next
        if not l1:
            point.next=l2
        else:
            point.next=l1
        return head.next

24 两两交换链表中的节点

思路:每次寻找相邻的两个节点,然后交换。

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        ListNode root = new ListNode(0);
        root.next = head;
        ListNode pre = root;
        
        while(pre.next != null && pre.next.next != null){
            ListNode start = pre.next;
            ListNode end = pre.next.next;
            
            pre.next = end;
            start.next = end.next;
            end.next = start;
            
            pre = start;
        }
        return root.next;
        
    }
}

25 K个一组翻转链表

思路:1.找到需要进行翻转的K个节点;2.进行翻转和递归;

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
         if(head == null || head .next == null){
            return head;
        }
        //不直接对head操作,这样可以保留一个头指针
        ListNode cur = head;
        // 记录节点个数
        int count = 0;
        // 找到需要进行翻转的K个节点
        while(cur != null && count != k){
            cur = cur.next;
            count ++;
        }
        // 进行特殊情况处理
        if(count == k){
            cur = reverseKGroup(cur, k);
            while(count -- > 0){
                ListNode tmp = head.next;
                head.next = cur; // 与后面翻转好的连接
                cur = head;
                head = tmp ;
            }
            head = cur;
        }
        return head;
        
    }
}

26 删除排序数组重复项

思路:双指针,数组完成排序后,我们可以放置两个指针 ii 和 jj,其中 ii 是慢指针,而 jj 是快指针。只要 nums[i] = nums[j]nums[i]=nums[j],我们就增加 jj 以跳过重复项。
当我们遇到 nums[j] 不等于 nums[i] 时,跳过重复项的运行已经结束,因此我们必须把它(nums[j]nums[j])的值复制到 nums[i +1]nums[i+1]。然后递增 ii,接着我们将再次重复相同的过程,直到 jj 到达数组的末尾为止。

public int removeDuplicates(int[] nums) {
    if (nums.length == 0) return 0;
    int i = 0;
    for (int j = 1; j < nums.length; j++) {
        if (nums[j] != nums[i]) {
            i++;
            nums[i] = nums[j];
        }
    }
    return i + 1;
}

27 移除元素

思路:与上一题类似采用双指针

public int removeElement(int[] nums, int val) {
    int i = 0;
    for (int j = 0; j < nums.length; j++) {
        if (nums[j] != val) {
            nums[i] = nums[j];
            i++;
        }
    }
    return i;
}

//优化
public int removeElement(int[] nums, int val) {
    int i = 0;
    int n = nums.length;
    while (i < n) {
        if (nums[i] == val) {
            nums[i] = nums[n - 1];
            // reduce array size by one
            n--;
        } else {
            i++;
        }
    }
    return n;
}

28 实现strStr()

思路:最好是采用KMP算法,一般的方法就是匹配。
更多思路参考:解题思路参考

class Solution {
    public int strStr(String haystack, String needle) {
        int res = -1;
        int len = needle.length();
        int lenhay = haystack.length();
        if ( lenhay < len )
            return -1;
        int star = 0 ;
        int end = len-1;
        while (end < lenhay){
             if (haystack.substring(star,end+1).equals(needle)){
                 return star;
             }
            end++;
            star++;
        }
        return -1;
    }
}

29 两数相除

描述:要求不使用乘法、除法和mod运算
思路:(1)倍增除数采用减法运算。

class Solution {
    public int divide(int dividend, int divisor) {
        int sign = (dividend ^ divisor) >> 31;
        long lDividend = Math.abs((long) dividend);
        long lDivisor = Math.abs((long) divisor);
        long res = 0;
        while (lDividend >= lDivisor){
            long tmp = lDivisor;
            long i = 1;
            while (lDividend >= tmp){
                lDividend -= tmp;
                res += i;
                i <<= 1;
                tmp <<= 1;
            }
        }
        if (sign == -1) res *= -1;
        if (res < Integer.MIN_VALUE) return Integer.MIN_VALUE;
        else if (res > Integer.MAX_VALUE) return Integer.MAX_VALUE;
        return (int)res;
        
    }
}

30 串联所有单词的子串

思路:滑动窗口。1由于每个单词长度一样,窗口向右扩展以单词为单位,当遇到不存在的单词,窗口清空,从下一个单词开始匹配
2当遇到重复出现的单词,窗口左侧收缩到第一次重复的位置的下一个单词,相当于窗口从左侧删除了重复单词
3当窗口长度等于单词总长度时,说明遇到了匹配的子串

public List<Integer> findSubstring(String s, String[] words) {
        if (s == null || words == null || words.length == 0)
            return new ArrayList<>();
        int wordLen = words[0].length();
        int len = words.length;
        int totalLen = wordLen * len;
        Map<String, Integer> map = new HashMap<>();
        Map<String, Integer> map2 = new HashMap<>();
        //把数组处理为Map提高检索速度
        for (String word : words) {
            if (map.containsKey(word)) {
                Integer value = map.get(word);
                value++;
                map.put(word, value);
            } else {
                map.put(word, 1);
            }
        }
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < wordLen; i++) {
            int index = i;
            for (int j = i; j <= s.length() - wordLen; j += wordLen) {
                String word = s.substring(j, j + wordLen);
                if (!map.containsKey(word)) {
                    //遇到不匹配的单词,清空map2,重新开始匹配
                    map2.clear();
                    index = j + wordLen;
                } else {
                    Integer value2 = map2.get(word);
                    if (value2 == null) {
                        map2.put(word, 1);
                    } else if (value2.equals(map.get(word))) {
                        //遇到重复次数过多的单词,index右移到第一次重复的位置,map2删除右移的单词
                        String temp;
                        while (!(temp = s.substring(index, index + wordLen)).equals(word)) {
                            map2.put(temp, map2.get(temp) - 1);
                            index += wordLen;
                        }
                        //删除第一个重复的单词,当前单词次数不变
                        index += wordLen;
                    } else {
                        map2.put(word, value2 + 1);
                    }
                    //判断子串长度是否匹配,匹配则说明遇到了一个匹配的下标
                    //下标右移一个单词的位置,map2去掉第一个单词
                    if (j + wordLen - index == totalLen) {
                        res.add(index);
                        String firstWord = s.substring(index, index + wordLen);
                        map2.put(firstWord, map2.get(firstWord) - 1);
                        index += wordLen;
                    }
                }
            }
            map2.clear();
        }
        return res;
    }

31 下一个排列

描述:给定一个排列求其下一个排列
思路:
先从右侧开始扫描找到第一对a[i] > a[i-1],然后从右侧找到第一个大于a[i-1]的数与a[i-1]交换,然后翻转i到最后的数即可。

public class Solution {
    public void nextPermutation(int[] nums) {
        int i = nums.length - 2;
        while (i >= 0 && nums[i + 1] <= nums[i]) {
            i--;
        }
        if (i >= 0) {
            int j = nums.length - 1;
            while (j >= 0 && nums[j] <= nums[i]) {
                j--;
            }
            swap(nums, i, j);
        }
        reverse(nums, i + 1);
    }

    private void reverse(int[] nums, int start) {
        int i = start, j = nums.length - 1;
        while (i < j) {
            swap(nums, i, j);
            i++;
            j--;
        }
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

32 最长有效括号

思路:(1)动态规划dp[i]表示以i结尾的最长有效子串长度。
参考思路
s[i] = ) && s[i-1]= ( dp[i] = dp[i-2]+2
s[i] = ) && s[i-1]= ) && s[i-dp[i-1]-1] = ( 那么:dp[i]=dp[i-1] + dp[i-dp[i-1]-2]+2;

public class Solution {
    public int longestValidParentheses(String s) {
        int maxans = 0;
        int dp[] = new int[s.length()];
        for (int i = 1; i < s.length(); i++) {
            if (s.charAt(i) == ')') {
                if (s.charAt(i - 1) == '(') {
                    dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
                } else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
                    dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
                }
                maxans = Math.max(maxans, dp[i]);
            }
        }
        return maxans;
    }
}

作者:LeetCode
链接:https://leetcode-cn.com/problems/longest-valid-parentheses/solution/zui-chang-you-xiao-gua-hao-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

(2)用栈进行模拟,每次遇到左括号就将下标入栈,遇到右括号我们弹出栈顶的元素并将当前元素的下标与弹出元素下标作差,得出当前有效括号字符串的长度。通过这种方法,我们继续计算有效子字符串的长度,并最终返回最长有效子字符串的长度。

public class Solution {

    public int longestValidParentheses(String s) {
        int maxans = 0;
        Stack<Integer> stack = new Stack<>();
        stack.push(-1);
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                stack.push(i);
            } else {
                stack.pop();
                if (stack.empty()) {
                    stack.push(i);
                } else {
                    maxans = Math.max(maxans, i - stack.peek());
                }
            }
        }
        return maxans;
    }
}

作者:LeetCode
链接:https://leetcode-cn.com/problems/longest-valid-parentheses/solution/zui-chang-you-xiao-gua-hao-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

(3)采用计数方式。左右两遍扫描。我们利用两个计数器 leftleft 和 rightright 。首先,我们从左到右遍历字符串,对于遇到的每个 \text{‘(’}‘(’,我们增加 leftleft 计算器,对于遇到的每个 \text{‘)’}‘)’ ,我们增加 rightright 计数器。每当 leftleft 计数器与 rightright 计数器相等时,我们计算当前有效字符串的长度,并且记录目前为止找到的最长子字符串。如果 rightright 计数器比 leftleft 计数器大时,我们将 leftleft 和 rightright 计数器同时变回 00 。

public class Solution {
    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;
    }
}

作者:LeetCode
链接:https://leetcode-cn.com/problems/longest-valid-parentheses/solution/zui-chang-you-xiao-gua-hao-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

33 搜索旋转排序数组

思路:先找到分界点,然后再两次二分搜索

class Solution {
  int [] nums;
  int target;

  public int find_rotate_index(int left, int right) {
    if (nums[left] < nums[right])
      return 0;

    while (left <= right) {
      int pivot = (left + right) / 2;
      if (nums[pivot] > nums[pivot + 1])
        return pivot + 1;
      else {
        if (nums[pivot] < nums[left])
          right = pivot - 1;
        else
          left = pivot + 1;
      }
    }
    return 0;
  }

  public int search(int left, int right) {
    /*
    Binary search
    */
    while (left <= right) {
      int pivot = (left + right) / 2;
      if (nums[pivot] == target)
        return pivot;
      else {
        if (target < nums[pivot])
          right = pivot - 1;
        else
          left = pivot + 1;
      }
    }
    return -1;
  }

  public int search(int[] nums, int target) {
    this.nums = nums;
    this.target = target;

    int n = nums.length;

    if (n == 0)
      return -1;
    if (n == 1)
      return this.nums[0] == target ? 0 : -1;

    int rotate_index = find_rotate_index(0, n - 1);

    // if target is the smallest element
    if (nums[rotate_index] == target)
      return rotate_index;
    // if array is not rotated, search in the entire array
    if (rotate_index == 0)
      return search(0, n - 1);
    if (target < nums[0])
      // search in the right side
      return search(rotate_index, n - 1);
    // search in the left side
    return search(0, rotate_index);
  }
}

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

思路:二分查找的变形,两遍寻找。对于第一个位置,如果找到元素,查看元素前面是否相等,如果相等在前半个区间继续找。

35 搜索插入位置

思路:二分搜索寻找第一个大于或等于目标值的索引;

public class Solution3 {

    public int searchInsert(int[] nums, int target) {
        int len = nums.length;
        if (nums[len - 1] < target) {
            return len;
        }

        int left = 0;
        int right = len - 1;

        while (left <= right) {
            int mid = (left + right) / 2;
            // 等于的情况最简单,我们应该放在第 1 个分支进行判断
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                // 题目要我们返回大于或者等于目标值的第 1 个数的索引
                // 此时 mid 一定不是所求的左边界,
                // 此时左边界更新为 mid + 1
                left = mid + 1;
            } else {
                // 既然不会等于,此时 nums[mid] > target
                // mid 也一定不是所求的右边界
                // 此时右边界更新为 mid - 1
                right = mid - 1;
            }
        }
        // 注意:一定得返回左边界 left,
        // 如果返回右边界 right 提交代码不会通过
        // 【注意】下面我尝试说明一下理由,如果你不太理解下面我说的,那是我表达的问题
        // 但我建议你不要纠结这个问题,因为我将要介绍的二分查找法模板,可以避免对返回 left 和 right 的讨论

        // 理由是对于 [1,3,5,6],target = 2,返回大于等于 target 的第 1 个数的索引,此时应该返回 1
        // 在上面的 while (left <= right) 退出循环以后,right < left,right = 0 ,left = 1
        // 根据题意应该返回 left,
        // 如果题目要求你返回小于等于 target 的所有数里最大的那个索引值,应该返回 right

        return left;
    }
}

36 有效数独

思路:遍历一遍,采用三个hashmap类型数据记录行,列和小方格中的数字,如果出现重复就返回false。

37 解数独

思路:回溯+剪枝,比较复杂一般面试不会考察。

38 报数

思路:绕口,参考

39 组合综合

描述:给定一个没有重复元素的数组和一个目标值,找出数组中所有可以使数字和为目标值的组合。
思路:回溯+剪枝。

40 组合总和2

描述:元素只可使用一次
思路:回溯+剪枝+记录当前所用元素

41 缺失的第一个正数

思路:bitmap

42 接雨水

思路:双指针方式
解释思路

int trap(vector& height)
{
    int left = 0, right = height.size() - 1;
    int ans = 0;
    int left_max = 0, right_max = 0;
    while (left < right) {
        if (height[left] < height[right]) {
            height[left] >= left_max ? (left_max = height[left]) : ans += (left_max - height[left]);
            ++left;
        }
        else {
            height[right] >= right_max ? (right_max = height[right]) : ans += (right_max - height[right]);
            --right;
        }
    }
    return ans;
}

作者:LeetCode
链接:https://leetcode-cn.com/problems/trapping-rain-water/solution/jie-yu-shui-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

43 字符串相乘

思路:模拟乘法运算;两层循环计算然后再进位。

result[i+j] += a[i] * a[j];

44 通配符匹配

思路:动态规划or双指针

45 跳跃游戏2

描述:给定一个数组,其值表示在该位置可以跳的最远距离,求到达终点最小跳跃次数。
思路:贪心算法,每次都跳跃到能跳的更远的位置。

public int jump(int[] nums) {
    int end = 0;
    int maxPosition = 0; 
    int steps = 0;
    for(int i = 0; i < nums.length - 1; i++){
        //找能跳的最远的
        maxPosition = Math.max(maxPosition, nums[i] + i); 
        if( i == end){ //遇到边界,就更新边界,并且步数加一
            end = maxPosition;
            steps++;
        }
    }
    return steps;
}

作者:windliang
链接:https://leetcode-cn.com/problems/jump-game-ii/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-10/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

46 全排列

描述:给定一个没有重复数字的数组,求全排列;
思路:回溯

class Solution {
  public void backtrack(int n,
                        ArrayList<Integer> nums,
                        List<List<Integer>> output,
                        int first) {
    // if all integers are used up
    if (first == n)
      output.add(new ArrayList<Integer>(nums));
    for (int i = first; i < n; i++) {
      // place i-th integer first 
      // in the current permutation
      Collections.swap(nums, first, i);
      // use next integers to complete the permutations
      backtrack(n, nums, output, first + 1);
      // backtrack
      Collections.swap(nums, first, i);
    }
  }

  public List<List<Integer>> permute(int[] nums) {
    // init output list
    List<List<Integer>> output = new LinkedList();

    // convert nums into list since the output is a list of lists
    ArrayList<Integer> nums_lst = new ArrayList<Integer>();
    for (int num : nums)
      nums_lst.add(num);

    int n = nums.length;
    backtrack(n, nums_lst, output, 0);
    return output;
  }
}

作者:LeetCode
链接:https://leetcode-cn.com/problems/permutations/solution/quan-pai-lie-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

47 全排列2

描述:给定一个包含重复数字的数组求全排列;
思路:回溯+剪枝

48 旋转图像

思路:找规律变换

49 字母异位词分组

描述:给定一个字符串数组,将字母异位词分组
思路:(1)排序数组分类(2)计数,每个字符串用一组数组来标识

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        if (strs.length == 0) return new ArrayList();
        Map<String, List> ans = new HashMap<String, List>();
        for (String s : strs) {
            char[] ca = s.toCharArray();
            Arrays.sort(ca);
            String key = String.valueOf(ca);
            if (!ans.containsKey(key)) ans.put(key, new ArrayList());
            ans.get(key).add(s);
        }
        return new ArrayList(ans.values());
    }
}

作者:LeetCode
链接:https://leetcode-cn.com/problems/group-anagrams/solution/zi-mu-yi-wei-ci-fen-zu-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

50 POW(X,N)

思路:快速幂

class Solution {
    public double myPow(double x, int n) {
        long N = n;
        if (N < 0) {
            x = 1 / x;
            N = -N;
        }
        double ans = 1;
        double current_product = x;
        for (long i = N; i > 0; i /= 2) {
            if ((i % 2) == 1) {
                ans = ans * current_product;
            }
            current_product = current_product * current_product;
        }
        return ans;
    }
};

作者:LeetCode
链接:https://leetcode-cn.com/problems/powx-n/solution/powx-n-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的:(leetcode,数据结构与算法)