第五周LeetCode记录

10.13 21. 1位重复数字

给定正整数 N,返回小于等于 N 且具有至少 1 位重复数字的正整数的个数。

输入:20
输出:1
解释:具有至少 1 位重复数字的正数(<= 20)只有 11 。

输入:100
输出:10
解释:具有至少 1 位重复数字的正数(<= 100)有 11,22,33,44,55,66,77,88,99 和 100 。

输入:1000
输出:262

最优解

public int numDupDigitsAtMostN(int N) {
        return N - dp(N);
    }

    public int dp(int n) {
        List<Integer> digits = new ArrayList<>();
        while (n > 0) {
            digits.add(n % 10);
            n /= 10;
        }
        // k代表k的位数
      	// 设剩下的位置为i,剩下的数字为j,则不重复数字是在每一位依次填入与前几位不同的数字,
        // 即选取剩下的j个数字填入剩下的i个位置,即有A(j, i)种可能,最后将其累加就是不重复数字个数
        int k = digits.size();

        int[] used = new int[10];
        int total = 0;
				
        // 求位数小于k的不重复数字的个数:因为最高位总是为0,
        // 因此一开始剩下的数字j总是为9个(1-9),然后剩下的低位可选的数字总共有A(10-1,i)
        for (int i = 1; i < k; i++) {
            total += 9 * A(9, i - 1);
        }

      	// 位数等于k
        for (int i = k - 1; i >= 0; i--) {
            int num = digits.get(i);

            for (int j = i == k - 1 ? 1 : 0; j < num; j++) {
                if (used[j] != 0) {
                    continue;
                }
                total += A(10 - (k - i), i);
            }

            if (++used[num] > 1) {
                break;
            }

            if (i == 0) {
                total += 1;
            }
        }
        return total;
    }

    public int fact(int n) {
        if (n == 1 || n == 0) {
            return 1;
        }
        return n * fact(n - 1);
    }

    public int A(int m, int n) {
        return fact(m) / fact(m - n);
    }

总结

为什么要用反证法:可以用公式计算比较方便,排列组合。分情况讨论,一种是高位为0,一种是高位不为0.

10.14 22. 找不同

给定两个字符串 st,它们只包含小写字母。

字符串 *t* 由字符串 *s* 随机重排,然后在随机位置添加一个字母。

请找出在 t 中被添加的字母。

输入:s = "abcd", t = "abcde"
输出:"e"
解释:'e' 是那个被添加的字母

输入:s = "", t = "y"
输出:"y"

思路

用collections.Count统计各个字幕出现的次数,找出多的那一个。

我的解

class Solution:
    @classmethod
    def findTheDifference(self, s: str, t: str) -> str:
        from collections import Counter
        counter_1 = Counter(t)
        counter_1.subtract(s)
        for k,v in counter_1.items():
            if v == 1:
                return k
                

10.14 23. 判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

示例 1:
s = "abc", t = "ahbgdc"

返回 true.

示例 2:
s = "axc", t = "ahbgdc"

返回 false

思路

双指针算法

最优解

class Solution:
    @classmethod
    def isSubsequence(self, s: str, t: str) -> bool:
        len_s = len(s)
        len_t = len(t)
        i = j = 0
        while i < len_s and j < len_t:
            if s[i] == t[j]:
                i += 1
            j += 1
        return i == len_s

总结

利用双指针来判断子序列,切忌用遍历。

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

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]。

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

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

思路

双指针,分别从前后逐个遍历。

我的解

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        if len(nums) == 0:
            return [-1, -1]
        if len(nums) == 1:
            if nums[0] == target:
                return [0,0]

        i = 0
        j = len(nums) - 1

        res = [-1] * 2
        while i < len(nums) and i != j:
            if nums[i] == target:
                res[0] = i
                while nums[j] != target:
                    j -= 1
                res[-1] = j
                return res
            else:
                i += 1
                        
        if i == j and nums[i] == target:
            return [i,j]
            
        return res
        
  优化
  class Solution:
    def searchRange(self, nums, target):
        # find the index of the leftmost appearance of `target`. if it does not
        # appear, return [-1, -1] early.
        for i in range(len(nums)):
            if nums[i] == target:
                left_idx = i
                break
        else:
            return [-1, -1]

        # find the index of the rightmost appearance of `target` (by reverse
        # iteration). it is guaranteed to appear.
        for j in range(len(nums)-1, -1, -1):
            if nums[j] == target:
                right_idx = j
                break

        return [left_idx, right_idx]

最优解

class Solution:
    # returns leftmost (or rightmost) index at which `target` should be inserted in sorted
    # array `nums` via binary search.
    def extreme_insertion_index(self, nums, target, left):
        lo = 0
        hi = len(nums)

        while lo < hi:
            mid = (lo + hi) // 2
            if nums[mid] > target or (left and target == nums[mid]):
                hi = mid
            else:
                lo = mid+1

        return lo


    def searchRange(self, nums, target):
        left_idx = self.extreme_insertion_index(nums, target, True)

        # assert that `left_idx` is within the array bounds and that `target`
        # is actually in `nums`.
        if left_idx == len(nums) or nums[left_idx] != target:
            return [-1, -1]

        return [left_idx, self.extreme_insertion_index(nums, target, False)-1]

总结

因为题设排序数组,即可以使用二分法查找。

10.18 25. 超级丑数

编写一段程序来查找第 *n* 个超级丑数。

超级丑数是指其所有质因数都是长度为 k 的质数列表 primes 中的正整数。

输入: n = 12, primes = [2,7,13,19]
输出: 32 
解释: 给定长度为 4 的质数列表 primes = [2,7,13,19],前 12 个超级丑数序列为:[1,2,4,7,8,13,14,16,19,26,28,32] 。

思路

见下一题思路

我的解

class Solution:
    def nthSuperUglyNumber(self, n: int, primes: List[int]) -> int:
        pointer_list = [0] * len(primes)
        res = [0] * n
        res[0] = 1

        for i in range(1, len(res)):
            res[i] = min(primes[j] * res[pointer_list[j]] for j in range(len(primes)))
            for index, value in enumerate(primes):
                if res[i] == value * res[pointer_list[index]]:
                    pointer_list[index] += 1
        return res[-1]

10.18 26. 下一个丑数

编写一个程序,找出第 n 个丑数。

丑数就是质因数只包含 2, 3, 5正整数

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

最优解

解法一:动态规划

此题丑数就是2,3,5的排列组合所能组成的公倍数由小到大排成的数组

class Solution:
    @classmethod
    def nthUglyNumber(self, n: int) -> int:
        i2 = i3 = i5 = 0
        dp = [0] * n
        dp[0] = 1
        for i in range(1, len(dp)):
            dp[i] = min(dp[i2] * 2, dp[i3] * 3, dp[i5] * 5)
            if dp[i] == dp[i2] * 2:
                i2 += 1
            if dp[i] == dp[i3] * 3:
                i3 += 1
            if dp[i] == dp[i5] * 5:
                i5 += 1
        return dp[-1]


if __name__ == '__main__':
    res = Solution.nthUglyNumber(10)
    print(res)

点击查看算法动画

解法二:堆算法
from heapq import heappop, heappush
class Ugly:
    def __init__(self):
        seen = {1, }
        self.nums = nums = []
        heap = []
        heappush(heap, 1)

        for _ in range(1690):
            curr_ugly = heappop(heap)
            nums.append(curr_ugly)
            for i in [2, 3, 5]:
                new_ugly = curr_ugly * i
                if new_ugly not in seen:
                    seen.add(new_ugly)
                    heappush(heap, new_ugly)
    
class Solution:
    u = Ugly()
    def nthUglyNumber(self, n):
        return self.u.nums[n - 1]

总结

丑数的定义要明确,是所给质因子公倍数的排序数组。

原文地址:https://www.cnblogs.com/jimmyhe/p/13873375.html