LeetCode算法笔记

 


1.两数之和

暴力法:
class
Solution { public int[] twoSum(int[] nums, int target) { for (int i = 0; i < nums.length; i++) { for (int j = i + 1; j < nums.length; j++) { if (nums[j] == target - nums[i]) { return new int[] { i, j }; } } } throw new IllegalArgumentException("No two sum solution"); } }
两遍哈希表
class
Solution {
public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            map.put(nums[i], i);
        }
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement) && map.get(complement) != i) {
                return new int[] { i, map.get(complement) };
            }
        }
        throw new IllegalArgumentException("No two sum solution");
    }
}
一遍哈希表
class
Solution { public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int complement = target - nums[i]; if (map.containsKey(complement)) { return new int[] { map.get(complement), i }; } map.put(nums[i], i); } throw new IllegalArgumentException("No two sum solution"); } }

 2.两数之和

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。假设除了数字 0 之外,这两个数都不会以 0 开头。

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

这里用到了一个链表游标的功能。

3.字符串相乘

给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。

class Solution {
    public String multiply(String num1, String num2) {
        /**
        num1的第i位(高位从0开始)和num2的第j位相乘的结果在乘积中的位置是[i+j, i+j+1]
        例: 123 * 45,  123的第1位 2 和45的第0位 4 乘积 08 存放在结果的第[1, 2]位中
          index:    0 1 2 3 4  
              
                        1 2 3
                    *     4 5
                    ---------
                          1 5
                        1 0
                      0 5
                    ---------
                      0 6 1 5
                        1 2
                      0 8
                    0 4
                    ---------
                    0 5 5 3 5
        这样我们就可以单独都对每一位进行相乘计算把结果存入相应的index中        
        **/
        
        int n1 = num1.length()-1;
        int n2 = num2.length()-1;
        if(n1 < 0 || n2 < 0) return "";
        int[] mul = new int[n1+n2+2];
        
        for(int i = n1; i >= 0; --i) {
            for(int j = n2; j >= 0; --j) {
                int bitmul = (num1.charAt(i)-'0') * (num2.charAt(j)-'0');      
                bitmul += mul[i+j+1]; // 先加低位判断是否有新的进位
                
                mul[i+j] += bitmul / 10;
                mul[i+j+1] = bitmul % 10;
            }
        }
        
        StringBuilder sb = new StringBuilder();
        int i = 0;
        // 去掉前导0
        while(i < mul.length-1 && mul[i] == 0) 
            i++;
        for(; i < mul.length; ++i)
            sb.append(mul[i]);
        return sb.toString();
    }
}

4.无重复字符的最长字串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

暴力法:

思路:逐个检查所有的子字符串,看它是否不含有重复的字符。

算法:假设我们有一个函数 boolean allUnique(String substring) ,如果子字符串中的字符都是唯一的,它会返回 true,否则会返回 false。 我们可以遍历给定字符串 s 的所有可能的子字符串并调用函数 allUnique。 如果事实证明返回值为 true,那么我们将会更新无重复字符子串的最大长度的答案。

现在让我们填补缺少的部分:

1.为了枚举给定字符串的所有子字符串,我们需要枚举它们开始和结束的索引。假设开始和结束的索引分别为 i 和 j。那么我们有0≤i<j≤n(这里的结束索引 j 是按惯例排除的)。因此,使用 i 从 0 到 n - 1以及 j从 i+1到 n这两个嵌套的循环,我们可以枚举出 s 的所有子字符串。

2.要检查一个字符串是否有重复字符,我们可以使用集合。我们遍历字符串中的所有字符,并将它们逐个放入 set 中。在放置一个字符之前,我们检查该集合是否已经包含它。如果包含,我们会返回 false。循环结束后,我们返回 true。

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        int ans = 0;
        for (int i = 0; i < n; i++)
            for (int j = i + 1; j <= n; j++)
                if (allUnique(s, i, j)) ans = Math.max(ans, j - i);
        return ans;
    }

    public boolean allUnique(String s, int start, int end) {
        Set<Character> set = new HashSet<>();
        for (int i = start; i < end; i++) {
            Character ch = s.charAt(i);
            if (set.contains(ch)) return false;
            set.add(ch);
        }
        return true;
    }
}

解题思路:考虑使用滑动窗口方法,通过使用 HashSet 作为滑动窗口,我们可以用 O(1)的时间来完成对字符是否在当前的子字符串中的检查。

滑动窗口是数组/字符串问题中常用的抽象概念。 窗口通常是在数组/字符串中由开始和结束索引定义的一系列元素的集合,即 [i, j)左闭,右开)。而滑动窗口是可以将两个边界向某一方向“滑动”的窗口。例如,我们将 [i, j)向右滑动 1 个元素,则它将变为 [i+1, j+1)(左闭,右开)。

回到我们的问题,我们使用 HashSet 将字符存储在当前窗口 [i, j)(最初 j = i)中。 然后我们向右侧滑动索引 j,如果它不在 HashSet 中,我们会继续滑动 j。直到 s[j] 已经存在于 HashSet 中。此时,我们找到的没有重复字符的最长子字符串将会以索引 i 开头。如果我们对所有的 i这样做,就可以得到答案。

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        Set<Character> set = new HashSet<>();
        int ans = 0, i = 0, j = 0;
        while (i < n && j < n) {
            // try to extend the range [i, j]
            if (!set.contains(s.charAt(j))){
                set.add(s.charAt(j++));
                ans = Math.max(ans, j - i);
            }
            else {
                set.remove(s.charAt(i++));
            }
        }
        return ans;
    }
}

优化的滑动窗口
上述的方法最多需要执行 2n 个步骤。事实上,它可以被进一步优化为仅需要 n 个步骤。我们可以定义字符到索引的映射,而不是使用集合来判断一个字符是否存在。 当我们找到重复的字符时,我们可以立即跳过该窗口。也就是说,如果 s[j] 在 [i, j)范围内有与 j'重复的字符,我们不需要逐渐增加 i 。 我们可以直接跳过 [i,j'] 范围内的所有元素,并将 i 变为 j' + 1

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

扩展:Java(假设字符集为 ASCII 128)

以前的我们都没有对字符串 s 所使用的字符集进行假设。

当我们知道该字符集比较小的时侯,我们可以用一个整数数组作为直接访问表来替换 Map。

常用的表如下所示:

  • int [26] 用于字母 ‘a’ - ‘z’ 或 ‘A’ - ‘Z’
  • int [128] 用于ASCII码
  • int [256] 用于扩展ASCII码
public class Solution {
    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;
    }
}

5.寻找两个有序数组的中位数

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。

请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

你可以假设 nums1 和 nums2 不会同时为空。

方:创建一个数组长度为mid+1的数组nums,如果两个数组的长度为偶数,则结果为(nums[mid-1]+nums[mid])/2,如果两个数组的长度为奇数,则结果为nums[mid];
nums数组创建的思路:比较两个数组,按照从小到大的顺序排列。
class
Solution { public double findMedianSortedArrays(int[] nums1, int[] nums2) { int len = nums1.length + nums2.length; int mid = len/2; int flag = 0; if(len%2==0){ flag = 1; } int[] nums = new int[mid + 1]; int i = 0, j = 0; for(int k = 0;k < mid+1; k++){ if(i < nums1.length && j < nums2.length){ nums[k] = nums1[i] < nums2[j]? nums1[i++]: nums2[j++]; }else if(i < nums1.length && j >= nums2.length){ nums[k] = nums1[i++]; }else if(j < nums2.length && i >= nums1.length){ nums[k] = nums2[j++]; } } if(flag > 0){ return (nums[mid-1] + nums[mid]) / 2.0; } return nums[mid]; } }

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

6.最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

方:这里使用窗口滑动方法,求最长回文,窗口大小考虑从大到小设置,首先让窗口大小设置为s.length(),逐个比较两边元素是否相等。
class
Solution { public String longestPalindrome(String s) { if(s.length() == 1 || "".equals(s)) return s; int len = s.length(); int subLen = len; while(subLen>0){ for(int i = 0; i + subLen <= len; i++){ String subStr = s.substring(i, i + subLen); int midLen = (subLen + 1) / 2; for(int k = 0; k < midLen; k++){ if(subStr.charAt(k) != subStr.charAt(subLen - k - 1)){ break; } if(k == midLen-1){ return subStr; } } } subLen--; } return ""; } }
扩展中心法:
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;//这里-1是因为这个时候的R和L超出了回文的范围,且这个时候已经不是回文了 }
 
 
public String Manacher(String s) {
        if (s.length() < 2) {
            return s;
        }
        StringBuilder sb = new StringBuilder();
        sb.append("^");
        for(int i=0; i < s.length(); i++){
            sb.append("#" + s.charAt(i));
        }
        sb.append("#$");
        String t = sb.toString();
        
        int len = t.length();
        int[] p = new int[len];
        int mx = 0, id = 0, maxLength=-1;
        int index = 0;
        for(int i = 1; i < len - 1; i++){
            p[i] = mx > i? Math.min(p[2*id-i], mx-i):1;
            while(t.charAt(i + p[i]) == t.charAt(i - p[i])){
                p[i]++;
            }
            if(mx < i + p[i]){
                mx = i + p[i];
                id = i;
            }
            if(maxLength < p[i] - 1 ){
                index = i;
                maxLength = p[i] - 1;
            }
        }
        int start = (index - p[index]) / 2;
        return s.substring(start, start + maxLength);
    }
 

 7.Z字形变换

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。

思路:通过从左向右迭代字符串,我们可以轻松地确定字符位于 Z 字形图案中的哪一行。
算法:我们可以使用min(numRows,len(s)) 个列表来表示 Z 字形图案中的非空行。
从左到右迭代s,将每个字符添加到合适的行。可以使用当前行和当前方向这两个变量对合适的行进行跟踪。
只有当我们向上移动到最上面的行或向下移动到最下面的行时,当前方向才会发生改变。
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();
    }
思路:按照与逐行读取 Z 字形图案相同的顺序访问字符串。
算法:首先访问 行 0 中的所有字符,接着访问 行 1,然后 行 2,依此类推...
对于所有整数 k,
行 0 中的字符位于索引 k(2⋅numRows−2) 处;
行numRows−1 中的字符位于索引 k(2⋅numRows−2)+numRows−1 处;
内部的 行 i 中的字符位于索引 k(2⋅numRows−2)+i 以及(k+1)(2⋅numRows−2)−i 处;
class Solution {
public String convert(String s, int numRows) {

        if (numRows == 1) return s;

        StringBuilder ret = new StringBuilder();
        int n = s.length();
        int cycleLen = 2 * numRows - 2;

        for (int i = 0; i < numRows; i++) {
            for (int j = 0; j + i < n; j += cycleLen) {
                ret.append(s.charAt(j + i));
                if (i != 0 && i != numRows - 1 && j + cycleLen - i < n)
                    ret.append(s.charAt(j + cycleLen - i));
            }
        }
        return ret.toString();
    }
}

 8.整数反转

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。

注意:假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−2^31,  2^31 − 1]-->[-2147483648, 2147483647]。请根据这个假设,如果反转后整数溢出那么就返回 0。

该算法定义了一个Long int类型数据,Long是一个64位的整数,因此,可以存放下32位有符号整数。先使用Long存储,最后结果判断范围。
该算法因为定义了一个Long类型,需要占用更多的内存。
public
int reverse(int x) { Long result = 0L ; for(;x!=0;x=x/10){ result = result*10+x%10; } return result>Integer.MAX_VALUE || result<Integer.MIN_VALUE? 0:result.intValue();//转换为int型
}
这里将int先转换为String类型,且会判断正负和最后一位是否为0;判断方法是取出String中的char字符,并进行比较。String-->StringBuilder,reverse()-->int.
此时可能会转换出错,因为超出int范围,这里我用try-catch进行回传,这种算法不合适
public
int reverse(int x){ if(x < 10 && x > -10) return x; String result = String.valueOf(x); boolean fu = false; if(result.charAt(0) == '-'){ fu = true; result = result.substring(1); } if(result.charAt(result.length() - 1) == '0'){ result = result.substring(0, result.length() - 1); } StringBuilder sb = new StringBuilder(result); sb = sb.reverse(); result = sb.toString(); if(fu){ try{ return -Integer.parseInt(result); }catch(Exception e){ return 0; } } try{ return Integer.parseInt(result); }catch(Exception e){ return 0; } }
 
  

方法:弹出和推入数字 & 溢出前进行检查
思路:我们可以一次构建反转整数的一位数字。在这样做的时候,我们可以预先检查向原整数附加另一位数字是否会导致溢出。

 
  

算法:反转整数的方法可以与反转字符串进行类比。

 
  

我们想重复“弹出”xx 的最后一位数字,并将它“推入”到rev 的后面。最后,rev 将与 x相反。

 
  

要在没有辅助堆栈/数组的帮助下 “弹出” 和 “推入” 数字,我们可以使用数学方法。

 
  
//pop operation:
pop = x % 10;
x /= 10;

//push operation:
temp = rev * 10 + pop;
rev = temp;  
 
  

但是,这种方法很危险,因为当 temp=rev⋅10+pop 时会导致溢出。

 
  

幸运的是,事先检查这个语句是否会导致溢出很容易。

 
  

为了便于解释,我们假设rev 是正数。

 
  
如果temp=rev⋅10+pop 导致溢出,那么一定有rev≥ INTMAX /10 。
如果rev> INTMAX/10,那么 temp=rev⋅10+pop 一定会溢出。
如果rev== INTMAX/10,那么只要pop > 7, temp=rev⋅10+pop 就会溢出。
 
  

rev 为负时可以应用类似的逻辑。

class Solution {

  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; } }
 

猜你喜欢

转载自www.cnblogs.com/ifreewolf/p/12382571.html