Tuesday, May 17, 2022

454. 4Sum II

二刷 06/2022

Version #2 K Sum II

本来以为挺简单的没想到其实有一些重点是想不到的

第一个就是如何用recursion来收集所有可能的sum,每一层都试着加入当前这个array所有的数,是O(N) time,k/2 层就是  O(N^(k / 2))

第二个非常重要就是一直TLE的reason是我把所有array分成了[0, length / 2]和[length /  2 + 1, length - 1] 两个部分,这样的分配是不均匀的,譬如当array数是4的时候就会分成[0, 1, 2]和[3]造成复杂度从O(N^2)飚到O(N^3)

为了平均分配应该是[0, length / 2 - 1]和[length / 2, length - 1]

Space O(k / 2) for recursion, O(N^(k/2)) for hashmap -> Total O(N^(k/2))

Runtime: 184 ms, faster than 58.71% of Java online submissions for 4Sum II.
Memory Usage: 57.3 MB, less than 59.43% of Java online submissions for 4Sum II.

class Solution {

    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {

        return kSumCount(new int[][]{nums1, nums2, nums3, nums4});

    }

    

    private int kSumCount(int[][] nums) {

        Map<Integer, Integer> firstHalfSums = new HashMap<>();

        countSum(nums, 0, nums.length / 2  - 1, 0, firstHalfSums);

        return countComplement(nums, nums.length / 2, nums.length - 1, 0, firstHalfSums);

    }

    

    private void countSum(int[][] nums, int start, int end, int sum, Map<Integer, Integer> sums) {

        if (start > end) {

            sums.put(sum, sums.getOrDefault(sum, 0) + 1);

            return;

        }

        for (int a : nums[start]) {

            countSum(nums, start + 1, end, sum + a, sums);

        }

    }

    

    private int countComplement(int[][] nums, int start, int end, int sum, Map<Integer, Integer> sums) {

        if (start > end) {

            return sums.getOrDefault(-sum, 0);

        }

        int cnt = 0;

        for (int a : nums[start]) {

            cnt += countComplement(nums, start + 1, end, sum + a, sums);

        }

        return cnt;

    }

}



一刷 05/2022

Version #1 HashMap

Time O(n^2)

Space O(n^2)

Runtime: 189 ms, faster than 44.39% of Java online submissions for 4Sum II.
Memory Usage: 58.3 MB, less than 33.53% of Java online submissions for 4Sum II.

class Solution {

    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {

        if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0 || nums3 == null || nums3.length == 0 || nums4 == null || nums4.length == 0) {

            return 0;

        }

        // nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

        // nums3[k] + nums4[l] = - (nums1[i] + nums2[j])


        // add all possible minus sum of nums1 and nums2 to a hashmap

        Map<Integer, Integer> target = new HashMap<>();

        for (int num1 : nums1) {

            for (int num2 : nums2) {

                target.put(num1 + num2, target.getOrDefault(num1 + num2, 0) + 1);

            }

        }

        int cnt = 0;

        for (int num3 : nums3) {

            for (int num4 : nums4) {

                cnt += target.getOrDefault(-num3-num4, 0);

            }

        }

        return cnt;

    }

}


18. 4Sum

二刷 06/2022

Version #2 K Sum

Time O(N^(k - 1))

Space O(k) for recursion

Runtime: 33 ms, faster than 43.23% of Java online submissions for 4Sum.
Memory Usage: 54.1 MB, less than 28.27% of Java online submissions for 4Sum.

class Solution {

    public List<List<Integer>> fourSum(int[] nums, int target) {

        Arrays.sort(nums);

        return kSum(nums, 0, (long)target, 4);

    }

    

    // Calculates k sum for subarray [start, len)

    private List<List<Integer>> kSum(int[] nums, int start, long target, int k) {

        if (k ==2) {

            return twoSum(nums, start, target);

        }

        List<List<Integer>> result = new ArrayList<>();

        for (int i = start; i < nums.length; i++) {

            if (i != start && nums[i] == nums[i - 1]) {

                continue;

            }

            for (List<Integer> sub : kSum(nums, i + 1, target - nums[i], k - 1)) {

                sub.add(nums[i]);

                result.add(sub);

            }

        }

        return result;

    }

    private List<List<Integer>> twoSum(int[] nums, int start, long target) {

        List<List<Integer>> result = new ArrayList<>();

        int end = nums.length - 1;

        while (start < end) {

            int sum = nums[start] + nums[end];

            if (sum < target) {

                start++;

            } else if(sum > target) {

                end--;

            } else {

                result.add(new ArrayList<>(Arrays.asList(nums[start], nums[end])));

                start++;

                while (start < end && nums[start] == nums[start - 1]) {

                    start++;

                }

            }

        }

        return result;

    }

}


Version #1 Two Pointers

比上次做这个题多了一个testcse

[1000000000,1000000000,1000000000,1000000000]
-294967296

这里target - nums[i] - nums[j] 会负数overflow的

加了一个cast成long的操作 long sum = (long)target - (first + second);

Time O(N^3)

Space O(1)

class Solution {

    public List<List<Integer>> fourSum(int[] nums, int target) {

        List<List<Integer>> result = new ArrayList<>();

        Arrays.sort(nums);

        for (int i = 0; i < nums.length; i++) {

            if (i != 0 && nums[i] == nums[i - 1]) {

                continue;

            }

            for (int j = i + 1; j < nums.length; j++) {

                if (j != i + 1 && nums[j] == nums[j - 1]) {

                    continue;

                }

                twoSum(nums, target, nums[i], nums[j], j + 1, result);

            }

        }

        return result;

    }

    

    private void twoSum(int[] nums, int target, int first, int second, int startIndex, List<List<Integer>> result) {

        long sum = (long)target - (first + second);

        int start = startIndex, end = nums.length - 1;

        while (start < end) {

            if (nums[start] + nums[end] <= sum) {

                // System.out.printf("nums[%d]+nums[%d]=%d\n", start, end, nums[start] + nums[end]);

                if (nums[start] + nums[end] == sum && (start == startIndex || nums[start] != nums[start - 1])) {

                    result.add(Arrays.asList(first, second, nums[start], nums[end]));

                }

                start++;

            } else {

                end--;

            }

        }

    }

}

一刷

Version #1 Two Pointers

Time O(n^3)

Space O(1)

Runtime: 17 ms, faster than 80.01% of Java online submissions for 4Sum.
Memory Usage: 44.6 MB, less than 52.39% of Java online submissions for 4Sum.

class Solution {

    public List<List<Integer>> fourSum(int[] nums, int target) {

        // Assuming a < b < c < d

        // nums[a] + nums[b] + nums[c] + nums[d] == target

        // nums[c] + nums[d] = target - (nums[a] + nums[b])

        //  For each combination of (a, b), try to find nums[c] and nums[d] that satisfy this criteria

        if (nums == null || nums.length < 4) {

            return new ArrayList<>();

        }

        Arrays.sort(nums);

        List<List<Integer>> result = new ArrayList<>();

        for (int i = 0; i < nums.length; i++) {

            if (i != 0 && nums[i] == nums[i - 1]) {

                continue;

            }

            for (int j = i + 1; j < nums.length; j++) {

                if (j != i + 1 && nums[j] == nums[j - 1]) {

                    continue;

                }

                twoSum(nums, target, i, j, result);

            }

        }

        return result;

    }

    

    private void twoSum(int[] nums, int target, int i, int j, List<List<Integer>> result) {

        int sum = target - nums[i] - nums[j];

        // Find pairs from [j + 1, nums.length] that sum up to the sum value

        int start = j + 1;

        int end = nums.length - 1;

        while (start < end) {

            if (nums[start] + nums[end] > sum) {

                end--;

                continue;

            }

            if (nums[start] + nums[end] < sum) {

                start++;

                continue;

            }

            result.add(Arrays.asList(nums[i], nums[j], nums[start], nums[end]));

            start++;

            while (start < end && nums[start] == nums[start - 1]) {

                start++;

            }

        }

    }

}



Version #2 HashMap version (with extra O(n) space) 

Time O(n^3)

Space O(n)

Runtime: 344 ms, faster than 8.96% of Java online submissions for 4Sum.
Memory Usage: 42.9 MB, less than 85.76% of Java online submissions for 4Sum.

class Solution {

    public List<List<Integer>> fourSum(int[] nums, int target) {

        if (nums == null || nums.length < 4) {

            return new ArrayList<>();

        }

        Arrays.sort(nums);

        List<List<Integer>> result = new ArrayList<>();

        for (int i = 0; i < nums.length; i++) {

            if (i != 0 && nums[i] == nums[i - 1]) {

                continue;

            }

            for (int j = i + 1; j < nums.length; j++) {

                if (j != i + 1 && nums[j] == nums[j - 1]) {

                    continue;

                }

                result.addAll(twoSum(nums, target, i, j));

            }

        }

        return result;

    }

    

    private List<List<Integer>> twoSum(int[] nums, int target, int i, int j) {

        Set<Integer> expected = new HashSet<>();

        List<List<Integer>> res = new ArrayList<>();

        for (int k = j + 1; k < nums.length; k++) {

            // If a number has been added to the result as the last number, skip it

            if (!res.isEmpty() && res.get(res.size() - 1).get(3) == nums[k]) {

                continue;

            }

            if (expected.contains(nums[k])) {

                res.add(Arrays.asList(nums[i], nums[j], target - nums[i] - nums[j] - nums[k], nums[k]));

            }

            expected.add(target - nums[i] - nums[j] - nums[k]);

        }

        return res;

    }

}


611. Valid Triangle Number

 一刷 05/2020

Version #1 Binary Search - Not Optimal

Time O(n^2logn)

Space O(1)

Runtime: 507 ms, faster than 20.28% of Java online submissions for Valid Triangle Number.
Memory Usage: 44.3 MB, less than 18.39% of Java online submissions for Valid Triangle Number.

class Solution {

    public int triangleNumber(int[] nums) {

        // triangle - given side lengths a <= b <= c, if a + b > c then they can make a triangle

        // Brute force O(n^3)

        // Binary search

        //      sort the array

        //      given a and b, find the largest index of c that can make a + b > c

        if (nums == null || nums.length < 3) {

            return 0;

        }

        int cnt = 0;

        Arrays.sort(nums);

        for (int i = 0; i < nums.length; i++) {

            for (int j = i + 1; j < nums.length; j++) {

                cnt += count(nums, i, j);

            }

        }

        return cnt;

    }

    

    private int count(int[] nums, int i, int j) {

        if (i >= j || j + 1 >= nums.length) {

            return 0;

        }

        // find the largest index k that make nums[i] + nums[j] > nums[k]

        // k > j && k < nums.length

        int sum = nums[i] + nums[j];

        int start = j + 1;

        int end = nums.length - 1;

        while (start + 1 < end) {

            int mid = start + (end - start) / 2;

            if (nums[mid] >= sum) {

                end = mid - 1;

            } else {

                start = mid;

            }

        }

        // From j + 1 to k, all nums[k] can form a triangle

        // count = k - (j + 1) + 1 = k - j

        if (nums[end] < sum) {

            return end - j;

        }

        if (nums[start] < sum) {

            return start - j;

        }

        return 0;

    }

}


Version # Two Pointers


Time O(n^2)

Space O(1)

Runtime: 40 ms, faster than 64.01% of Java online submissions for Valid Triangle Number.
Memory Usage: 43.8 MB, less than 52.58% of Java online submissions for Valid Triangle Number.

class Solution {

    public int triangleNumber(int[] nums) {

        // Assuming side lengths a <= b <= c, if a + b > c then they can form a triangle

        // Iterate through nums to select a number c

        // Try to find pairs on its left whose sum is larger than c

        if (nums == null || nums.length < 3) {

            return 0;

        }

        Arrays.sort(nums);

        int cnt = 0;

        // at least needs to be the 3rd number to have two numbers on its left

        for (int i = 2; i < nums.length; i++) {

            cnt += count(nums, i);

        }

        return cnt;

    }

    

    private int count(int[] nums, int targetIndex) {

        int start = 0, end = targetIndex - 1;

        int cnt = 0;

        while (start < end) {

            if (nums[start] + nums[end] > nums[targetIndex]) {

                // for all start < end, nums[start] + nums[end] is larger than nums[targetIndex]

                cnt += end - start;

                end--;

            } else {

                start++;

            }

        }

        return cnt;

    }

}

Monday, May 16, 2022

704. Binary Search

二刷 06/2022

Time O(logN)

Space O(1)

Runtime: 0 ms, faster than 100.00% of Java online submissions for Binary Search.
Memory Usage: 54.5 MB, less than 32.58% of Java online submissions for Binary Search.

class Solution {

    public int search(int[] nums, int target) {

        int start = 0, end = nums.length - 1;

        while (start <= end) {

            int mid = start + (end - start) / 2;

            if (nums[mid] == target) {

                return mid;

            }

            if (nums[mid] < target) {

                start = mid + 1;

            } else {

                end = mid - 1;

            }

        }

        return -1;

    }

} 


一刷 05/2022

Runtime: 0 ms, faster than 100.00% of Java online submissions for Binary Search.
Memory Usage: 42.6 MB, less than 95.81% of Java online submissions for Binary Search.

class Solution {

    public int search(int[] nums, int target) {

        if (nums == null || nums.length == 0) {

            return -1;

        }

        int start = 0, end = nums.length - 1;

        // Stop iteration when they are next to each other

        while (start + 1 < end) {

            int mid = start + (end - start) / 2;

            if (nums[mid] == target) {

                return mid;

            }

            if (nums[mid] < target) {

                start = mid + 1;

            } else {

                end = mid - 1;

            }

        }

        if (nums[end] == target) {

            return end;

        }

        if (nums[start] == target) {

            return start;

        }

        return -1;

    }

}

Friday, May 13, 2022

912. Sort an Array

 一刷

Version #1 Quick sort 

这个写的不好,看下边一个版本

有两个问题

1.不应该选取nums[start] 作为pivot,因为如果原有的nums是descending or ascending order, the time efficiency will be reduced

2.left++和right--的条件应该是大于或者小于

Runtime: 952 ms, faster than 16.06% of Java online submissions for Sort an Array.
Memory Usage: 72.9 MB, less than 17.00% of Java online submissions for Sort an Array.

Version #1 Quick Sort

class Solution {

    public int[] sortArray(int[] nums) {

        // Quick sort

        // Select the 1st element in the array, devide the array by comparing each element with the seleted element

        // start         end

        // nums[i] <= p    nums[i] >= p

        return sortArrayHelper(nums, 0, nums.length - 1);

    }

    //  s             e  r l

    // [0,1,1,1,1,1,1,1,1,6,5]

    private int[] sortArrayHelper(int[] nums, int start, int end) {

        if (start >= end) {

            return nums;

        }

        //                      lr

        // [(1),1,1,1,1,1,1,1,0,6,5]

        int left = start + 1, right = end;

        int p = nums[start];

        while (left <= right) {

            // left is the first character that's larger than p

            while (left <= right && nums[left] <= p) {

                left++;

            }

            while (left <= right && nums[right] >= p) {

                right--;

            }

            if (left <= right) {

                swap(nums, left, right);

                left++;

                right--;

            }

        }

        swap(nums, start, left - 1);

        sortArrayHelper(nums, start, left - 2);

        sortArrayHelper(nums, left, end);

        return nums;

    }

    

    private void swap(int[] nums, int i, int j) {

        int temp = nums[i];

        nums[i] = nums[j];

        nums[j] = temp;

    }

}


Version #1 Quick sort

Optimized version

Using nums[(start + end) / 2] as the pivot

This will improve the speed if the nums are in ascending or descending order

Runtime: 10 ms, faster than 88.81% of Java online submissions for Sort an Array.
Memory Usage: 50.8 MB, less than 97.00% of Java online submissions for Sort an Array.

class Solution {

    public int[] sortArray(int[] nums) {

        // Quick sort

        // Shuffle the array (optional)

        // Partition so that, for some j

        // - entry a[j] is in place

        // - no larger entry to the left of j

        // - no smaller entry to the right of j

        // Sort each piece recursively

        if (nums == null) {

            return null;

        }

        quickSort(nums, 0, nums.length - 1);

        return nums;

    }

    

    // Sort given array from index start to index end

    private void quickSort(int[] nums, int start, int end) {

        if (start >= end) {

            return;

        }

        int pivot = nums[start + (end - start) / 2];

        // partition the subarray between start and end

        // so that all nums <= pivot are on the left, all nums >= pivot are on the right

        int left = start, right = end;

        while (left <= right) {

            while (left <= right && nums[left] < pivot) {

                left++;

            }

            while (left <= right && nums[right] > pivot) {

                right--;

            }

            if (left <= right) {

                // swap nums[left] and nums[right]

                int temp = nums[left];

                nums[left] = nums[right];

                nums[right] = temp;

                left++;

                right--;

            }

        }

        // All nums to the left side of pointer l are smaller than pivot

        // All nums to the right side of pointer r are larger than pivot

        //       l

        //   r

        // 0 1 2 3

        quickSort(nums, start, right);

        quickSort(nums, left, end);

    }

}


Version #2 Merge Sort

每次都new了一个新的result数组,浪费了空间,同时time wasted during the construction of new temporary arrays

下面写了optimized solution

Runtime: 19 ms, faster than 52.47% of Java online submissions for Sort an Array.
Memory Usage: 72.8 MB, less than 17.01% of Java online submissions for Sort an Array.

class Solution {

    public int[] sortArray(int[] nums) {

        // Merge sort

        // Divide nums array from the middle, sort each subarray separately

        // Merge two arrays together

        if (nums == null) {

            return null;

        }

        return mergeSort(nums, 0, nums.length - 1);

    }

    

    private int[] mergeSort(int[] nums, int start, int end) {

        // exit criteria

        if (start > end) {

            return new int[0];

        }

        if (start == end) {

            return new int[]{nums[start]};

        }

        int[] leftNums = mergeSort(nums, start, (start + end) / 2);

        int[] rightNums = mergeSort(nums, (start + end) / 2 + 1, end);

        return merge(leftNums, rightNums);

    }

    

    private int[] merge(int[] nums1, int[] nums2) {

        int[] result = new int[nums1.length + nums2.length];

        int p1 = 0, p2 = 0, pr = 0;

        while (p1 < nums1.length && p2 < nums2.length) {

            if (nums1[p1] <= nums2[p2]) {

                result[pr] = nums1[p1];

                p1++;

                pr++;

            } else {

                result[pr] = nums2[p2];

                p2++;

                pr++;

            }

        }

        while (p1 < nums1.length) {

            result[pr] = nums1[p1];

            pr++;

            p1++;

        }

        while (p2 < nums2.length) {

            result[pr] = nums2[p2];

            pr++;

            p2++;

        }

        return result;

    }

}


Version #2 Merge Sort [Optimized]

To preserve stability, if two keys are equal, we always need to first choose from the left subarray

Runtime: 10 ms, faster than 88.81% of Java online submissions for Sort an Array.
Memory Usage: 51.2 MB, less than 91.77% of Java online submissions for Sort an Array.

class Solution {

    public int[] sortArray(int[] nums) {

        // Merge sort

        // Divide array into two halves

        // Recursively sort each half

        // Merge two halves

        if (nums == null) {

            return null;

        }

        // Create a auxiliary array to temporarily store the subarray

        int[] aux = new int[nums.length];

        quickSort(nums, 0, nums.length - 1, aux);

        return nums;

    }

    

    // quickSort - sort array from index start to index end

    private void quickSort(int[] nums, int start, int end, int[] aux) {

        // base case

        if (start >= end) { // stop when there's less than or equal to one element

            return;

        }

        int mid = start + (end - start) / 2;

        quickSort(nums, start, mid, aux);

        quickSort(nums, mid + 1, end, aux);

        // Optimize - if the largest element in the first half is smaller than the smallest element in the second half, no need to merge since the array is already sorted

        if (nums[mid] < nums[mid + 1]) {

            return;

        }

        merge(nums, start, mid, end, aux);

    }

    

    // merge - merge subarray [start, mid] with subarray [mid + 1, end]

    // Guarantee that the two subarrays are ordered

    private void merge(int[]nums, int start, int mid, int end, int[] aux) {

        // Firstly copy the original array to the auxiliary array

        for (int i = start; i <= end; i++) {

            aux[i] = nums[i];

        }

        // keep 3 pointers, pFirstHalf, pSecondHalf, pResult

        int pFirstHalf = start, pSecondHalf = mid + 1, pResult = start;

        while (pFirstHalf <= mid && pSecondHalf <= end) {

            // If two elements are equal, choose from the firsthalf

            if (aux[pFirstHalf] <= aux[pSecondHalf]) {

                nums[pResult] = aux[pFirstHalf];

                pFirstHalf++;

            } else {

                nums[pResult] = aux[pSecondHalf];

                pSecondHalf++;

            }

            pResult++;

        }

        while (pFirstHalf <= mid) {

            nums[pResult++] = aux[pFirstHalf++];

        }

        while (pSecondHalf <= end) {

            nums[pResult++] = aux[pSecondHalf++];

        }

    }

}

Thursday, May 12, 2022

680. Valid Palindrome II

 一刷 2020/05


Runtime: 10 ms, faster than 58.06% of Java online submissions for Valid Palindrome II.
Memory Usage: 54.7 MB, less than 42.39% of Java online submissions for Valid Palindrome II.

class Solution {

    public boolean validPalindrome(String s) {

        // Use two pointers scan towards each other

        // If the pointed characters are not equal, check if there's any remaining quota left to delete character

        // If yes, try to move each pointer to skip one character and check the remaining string

        if (s == null || s.equals("")) {

            return true;

        }

        int left = 0, right = s.length() - 1;

        int delete = 1;

        return validPalinHelper(s, left, right, delete);

    }

    

    private boolean validPalinHelper(String s, int left, int right, int delete) {

        while (left < right) {

            if (s.charAt(left) == s.charAt(right)) {

                left++;

                right--;

                continue;

            }

            if (delete <= 0) {

                return false;

            }

            return validPalinHelper(s, left  + 1, right, delete - 1) || validPalinHelper(s, left, right - 1, delete - 1);

        }

        return true;

    }

}

Monday, August 9, 2021

415. Add Strings

需要改进的地方是求carry

应该用

int value = (x1 + x2 + carry) % 10;

            carry = (x1 + x2 + carry) / 10;


 class Solution {

    public String addStrings(String num1, String num2) {

        // Assuming num1 is longer.

        if (num1.length() < num2.length()) {

            Solution s = new Solution();

            return s.addStrings(num2, num1);

        }

        StringBuilder sb = new StringBuilder();

        int l1 = num1.length(), l2 = num2.length();

        int carry = 0;

        for (int i = 0; i < l1; i++) {

            int v1 = num1.charAt(l1 - 1 - i) - '0';

            int v2 = i >= l2 ? 0: num2.charAt(l2 - 1 - i) - '0';

            int sum = v1 + v2 + carry;

            if (sum >= 10) {

                carry = 1;

                sum -=10;

            } else {

                carry = 0;

            }

            sb.append((char)('0' + sum));

        }

        if (carry != 0) {

            sb.append('1');

        }

        return sb.reverse().toString();

    }

    

}