[刷题] Leetcode算法 (2020-2-25)

1.两数之和

题目:

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

代码:

class Solution:
    def twoSum(self, nums, target):
        # 使用字典来保存 数字:索引 
        hashmap = {}
        # 创建 数字:索引 键值对
        for ind, num in enumerate(nums):
            hashmap[num] = ind

        # 循环,从中获取target-num对应的index,但是index不能和i相等,因为一个数不能使用两次
        for i, num in enumerate(nums):
            j = hashmap.get(target - num)
            if j is not None and i != j:
                return [i, j]


if __name__ == '__main__':
    s = Solution()
    print(s.twoSum([3, 3, 4, 3], 6))  # 返回[0,3],而不是[0,1]

总结:

# 该题可以使用暴力法来解答,即双层循环一个一个试,直到找到2个数加起来等于target,但是效率很低下
# 使用enumerate将数组转化为字典,利用字典的get方法,可以很快判断字典中是否存在想要的值,大大提高运行效率,但是会浪费内存空间

2.整数反转

题目:

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

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

示例:

输入123,输出321
输入-123,输出-321
输入120,输出20

代码1:

class Solution:
    def reverse(self, x: int):
        # 使用flag记录是正还是负,正为True,负为False
        flag = True
        # 先将x转换为正数,用flag记录符号
        if x < 0:
            flag = False
            x = -x
        # 数字转换为字符串
        num_str = str(x)
        # 将字符串翻转
        new_str = ""
        for c in num_str:
            new_str = c + new_str

        # 将翻转后的字符串转回整数
        new_num = int(new_str)
        # 恢复符号
        if not flag:
            new_num = -new_num
        # 判断边界
        if new_num > 2 ** 31 - 1 or new_num < -2 ** 31:
            return 0

        return new_num


if __name__ == '__main__':
    s = Solution()
    s.reverse(-321)

这是自己第一版的low代码,字符串翻转用了for循环。。

总结:

# 使用重新拼接字符串的形式进行字符串翻转,浪费了内存空间,比较low
# 使用了简单if else,比较low。完全可以使用三目来代替

代码2:

class Solution:
    def reverse(self, x: int) -> int:
        # 如果是个位数,直接返回
        if -10 < x < 10:
            return x
        # 整数转换为字符串
        str_x = str(x)
        # 判断第一个字符是否为'-'
        if str_x[0] != "-":
            # 如果不是负数,则直接翻转字符串
            str_x = str_x[::-1]
            # 转回数字
            x = int(str_x)
        else:
            # 如果是负数,排除符号进行翻转
            str_x = str_x[:0:-1]
            # 转换为正整数
            x = int(str_x)
            # 加上符号
            x = -x
        # 使用三目运算符
        return x if -2147483648 < x < 2147483647 else 0

参考别人的代码,原理也是使用字符串来翻转,但比我自己写的高级,使用切片操作的负步长来翻转。

总结:

# 字符串翻转可以使用 切片操作 负步长来进行,比较简洁,效率高
# 使用三目运算代替简单if else

代码3:

class Solution:
    def reverse(self, x: int):
        # y为x的绝对值,res初始化为0
        y, res = abs(x), 0
        # 则其数值范围为 [−2^31,  2^31 − 1],这里先不管符号,x为正时boundary=2**31-1,x为负时,boundary=2**31
        boundry = (1 << 31) - 1 if x > 0 else 1 << 31
        # 循环翻转,从最后一位数开始
        while y != 0:
            # res是逐位翻转后的结果,每次乘以十再加上新的一位翻转数,y%10取余得到的是当前最后一位数
            res = res * 10 + y % 10
            if res > boundry:
                return 0
            # 循环一次,y整除10(即砍掉最后已经翻转的数字)
            y //= 10
        # 恢复符号
        return res if x > 0 else -res


if __name__ == '__main__':
    s = Solution()
    print(s.reverse(-321))

使用逐位翻转的方式,没翻转一位就和边界比较。

总结:

# 1<<31就是2**31
# 使用逐位翻转,直接从一个整数计算出翻转后的整数,运算速度快,节省空间。

3.回文数

题目:

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

示例:

输入: 121
输出: true

输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。

输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。

代码1:

class Solution:
    def isPalindrome(self, x: int) -> bool:
        # 如果是负数,则一定不是回文数
        if x<0:
            return False
        
        # 转换为字符串
        str_num = str(x)
        # 字符串长度
        length = len(str_num)
        # 砍半
        harf = length//2
        # 默认为True
        flag = True
        # 循环,判断对称位置的字符是否相同
        for i in range(harf):
            # 如果不同,则flag=Flase,并停止循环
            if str_num[i] != str_num[length-1-i]:
                flag = False
                break
        # 返回flag
        return flag

第一个实现的版本,将数字转换为字符串来对比对称位置的字符是否相等来判断。比较low的思路。

代码2:

class Solution:
    def isPalindrome(self, x: int) -> bool:
        # 负数直接返回false
        if x<0:
            return False
        # 转换为字符串
        str_num = str(x)
        # 翻转字符串
        str_num_re = str_num[::-1]
        # 比较字符串,返回结果
        return str_num == str_num_re

直接翻转字符串比较是否相同。

代码3:

class Solution:
    def isPalindrome(self, x: int) -> bool:
        # x小于0,则返回false
        if x < 0 :
            return False
        # 定义数字翻转函数
        def reverse(y:int):
            res = 0
            while True:
                res = res*10 + y%10
                if y//10 == 0:
                    break
                else:
                    y = y//10
            return res
        # 翻转后的数字如果等于原数字,则是回文数
        return True if reverse(x)==x else False

将数字翻转,比较翻转后的数字和原数字是否相等来判断是否为回文数。效率也不高。

总结:

# 以上三种代码,原理都很简单,一个是比较对称位置数是否相同,二是比较翻转后的数字或字符串是否相同

4.罗马数字转整数

题目:

罗马数字包含以下七种字符: IVXLCD 和 M

字符          数值
I             1
V             5
X             10
L             50
C             100
D             500
M             1000

例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做  XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。

示例:

输入: "III"
输出: 3

输入: "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.

输入: "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.

代码1:

class Solution:
    def romanToInt(self, s: str) -> int:
        # 所有罗马数字
        roma_num = {'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}
        # 特殊组合
        special_num = {'IV':4,'IX':9,'XL':40,'XC':90,'CD':400,'CM':900}

        # res用于记录总数
        res = 0
        # i为index
        i = 0
        while i<len(s):
            # 当为最后一个字符时,不会再出现特殊组合,直接加其值即可
            if i == len(s)-1:
                res += roma_num.get(s[-1])
                break
            # 判断i位置和i+1位置的两个字符组合起来是不是特殊组合
            spe_num = special_num.get(s[i:i+2])
            # 如果是特殊组合,则从special_num中获取值,i+=2
            if spe_num != None:
                i += 2
                res += spe_num
            # 如果不是特殊组合,则从roma_num中获取i位字符的值,i+=1
            else:
                res += roma_num.get(s[i])
                i+=1

        return res

最简单粗暴的做法,穷举每一位字符,并且检查其与右边字符组合是否为特殊组合。分别取值相加即可。

代码2:

class Solution:
    def romanToInt(self, s: str) -> int:
        # 所有罗马数字
        roma_num = {'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}
        
        res = 0
        for idx in range(len(s)):
            # 获取当前idx位置的值
            curr_num = roma_num.get(s[idx])
            # 如果后面还有字符
            if idx < len(s)-1:
                # 获取idx+1位置的值
                next_num = roma_num.get(s[idx+1])

                # 如果idx位置的值小于idx+1位置的值,则为负。例如IV中的I<V,则I=-1,IV=5-1=4
                if curr_num < next_num:
                    res -= curr_num
                else:
                    # 如果idx位置的值大于等于idx+1位置的值,则为正
                    res += curr_num
            else:
                # 最后一位字符,直接获取值加到总值中
                res += curr_num
        
        return res

找到罗马数字中的规律,即IV、IX、XL、XC、CD、CM都是左边罗马字对应的值小于右边罗马字对应的值,例如I小于V,X小于L。只要出现左边小于右边的情况,左边的数就为负。

总结:

# 这种字符转数字的题目,一般都会用到字段来映射。
# 还有就是要注意找到特别的规律

5.最长公共前缀

题目:

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""

示例:

输入: ["flower","flow","flight"]
输出: "fl"

输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀

代码1:

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        res = ""
        # 如果strs为空,直接返回""
        if len(strs) == 0:
            return ""

        # 获取所有字符串的长度,取最小长度
        lens = []
        for s in strs:
            lens.append(len(s))
        min_len = min(lens)
        
        # 循环获取所有字符串的每一位字符,每次都存放到一个set中(自动去重)
        i = 0
        curr = set()
        while i < min_len:
            for s in strs:
                curr.add(s[i])    
            i+=1
            # 如果set中只有一个字符,说明该位字符属于公共前缀
            if len(curr) == 1:
                # 获取该公共前缀字符拼接到结果中
                res += curr.pop()
            else:
                # 如果set中的字符大于1,则说明最长公共前缀已结束
                break
        
        return res

这里利用了set的自动去重功能,只需要检查set中元素个数,为1表示大家相等,大于1表示有异数。

代码2:

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:     
        s = ""
        # 使用zip将所有字符串的每一位组合成元组(默认按最短字符串长度)
        # 例如["alex","alenfj","ale123"] ---> [('a','a','a'),('l','l','l'),('e','e','e'),('x','n','1')]
        for i in zip(*strs):
            # i就为其中一个元组
            # 将元组转换为set,看其种元素个数,如果为1,则元组中元组都相同
            if len(set(i)) == 1:
                s += i[0]
            else:
                break           
        return s

利用zip函数来直接将字符串按列拼接成元组。更加简洁。

6.有效的括号

题目:

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

有效字符串需满足:

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例:

输入: "()"
输出: true

输入: "()[]{}"
输出: true

输入: "([)]"
输出: false

输入: "{[]}"
输出: true

代码:

class Solution:
    def isValid(self, s: str) -> bool:
        # 左右括号映射
        map_dict={'(':')','[':']','{':'}'}
        # 使用一个list模拟栈(先进后出)
        stack = []
        # 循环每一个括号
        for c in s:
            # 看栈中是否为空,如果不为空,则将c与stack中最后一个元素比对,看是否成对
            if len(stack) != 0:
                last = stack[-1]
                # 配对成功,则弹出stack中的左括号
                if c == map_dict.get(last):
                    stack.pop()
                else:  # 配对失败,则将c压入stack
                    stack.append(c)
            else:  # 栈中为空,则无法比对,直接将c压入栈
                stack.append(c)
        
        # 当所有的括号都配对成功,即stack最终为空时,返回True,否则返回False
        return True if len(stack)==0 else False

这种解法,主要是利用栈的先进后出特性,匹配相邻两个括号是否配对,如果配对,则弹出。一层一层配对完毕,stack应该为空。

7.合并两个有序链表

题目:

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

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

代码1:

class Solution :
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        # 先定义一个头结点,返回结果时不需要该头结点
        prehead = ListNode(-1)

        # 使用一个临时变量来作为指针
        prev = prehead
        # 将l1 and l2作为while循环条件,只要有一方为空,就不用再比较大小了,直接可以将还有元素的一方加入到链表最后
        while l1 and l2:
            # 谁小谁加入prev.next
            if l1.val <= l2.val:
                prev.next = l1
                l1 = l1.next
            else: 
                prev.next = l2
                l2 = l2.next
            # 每次加一个元素,prev也要向后移一位
            prev = prev.next
        
        # 如果l2为空,则把l1加到prev后面,否则将l2加到prev后
        prev.next = l1 if not l2 else l2
        
        # 返回除去头结点的结果链表
        return prehead.next;

这是循环迭代解法,利用循环,直到某个链表元素被取完。然后将还有元素的链表直接挂到最后。

总结:

# 这里使用l1 and l2作为while循环的条件,将需要比较大小的部分全部放在循环中执行

代码2:

class Solution:
    def mergeTwoLists(self, l1, l2):
        # 递归结束条件1,l1为空时,返回l2
        if l1 is None:
            return l2
        # 递归结束条件2,l2为空时,返回l1
        elif l2 is None:
            return l1
        # 如果两个都还有元素,则比较大小,递归的将每次比较的最小值接到当前节点后面
        elif l1.val < l2.val:
            l1.next = self.mergeTwoLists(l1.next, l2)
            return l1
        else:
            l2.next = self.mergeTwoLists(l1, l2.next)
            return l2

递归解法,最难理解的就是黄色部分。其实就是在l1和l2都还有元素的时候,两两比较大小,取小的那个放到链表的后面,经过递归的过程,慢慢串成结果链表。

8.删除排序数组中的重复项

题目:

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例:

给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。

代码:

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        # 从一个元素开始
        i = 1
        # 获取初始nums的长度
        length = len(nums)
        # 从1开始循环元素
        while i < length:
            # 如果当前元素和前一个元素相等,则删除当前元素,然后总长度length-=1
            if nums[i] == nums[i-1]:
                del nums[i]
                length -= 1
            # 如果不相等,则循环下一个元素
            else:
                i += 1
        # 返回最终list长度
        return len(nums)

在当前list空间直接相邻做对比,如果和前一个元素一样,则删除当前元素,记得总长度要调整。

9.移除元素

题目:

给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例:

给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。

代码1:

class Solution:
    def removeElement(self, nums, val):
        # 如果nums为空,则返回0
        if len(nums) == 0:
            return 0
        
        # idx为从左到右的索引
        idx = 0
        # idx_re为从右到左的索引
        idx_re = len(nums) - 1
        # 总长度
        length = len(nums)
        
        while idx < length:
            # 如果元素等于val
            if nums[idx] == val:
                # 开始从后面循环,找到一个不等于val的位置
                while True:
                    # 如果idx和idx_re碰头了,则已经交换完毕
                    if idx == idx_re:
                        # 返回需要的长度
                        return idx
                    # 如果从右边开始的元素等于val,则向前移动一位,再次检测
                    if nums[idx_re] == val:
                        idx_re -= 1
                        continue
                    # 当找到一个不等于val的位置,交换idx和idx_re位置的元素
                    else:
                        nums[idx], nums[idx_re] = nums[idx_re], nums[idx]
                        break
            # 循环下一个元素
            idx += 1

比较笨的办法,将检测到的等于val的元素交换到最后,前面留下全部需要的元素。这种方法不破坏整个数组的元素个数(没这个要求)。

代码2:

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        flag = 0  # 定义一个指针标志
        for num in nums:
            # 当元素不等于val的时候,直接从前面开始覆盖(不会影响数据的正确性)
            if num != val:
                nums[flag] = num  # 覆盖数组,得到「题目要求的数组」
                flag += 1  # 指针后移
        return flag   # 返回「题目要求的数组」的长度

比较巧妙的方法,不考虑数据是否会被破坏,因为破坏的都是不需要的数据。直接从前面开始覆盖即可。

总结:

# 当题目要求,不需要考虑不需要元素的时候,可以采取比较粗暴的覆盖办法。这种办法往往比较简便清晰

10.实现strStr()

题目:

实现 strStr() 函数。

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。

对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。

示例:

输入: haystack = "hello", needle = "ll"
输出: 2

输入: haystack = "aaaaa", needle = "bba"
输出: -1

代码1:

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        # 先获取两个字符串的长度
        hay_len = len(haystack)
        nee_len = len(needle)
        
        # 当needle长度大于kaystack长度,则直接返回-1
        if nee_len > hay_len:
            return -1
        # 当needle长度为0,返回0
        if nee_len == 0:
            return 0
        
        # 循环切片(needle大小),然后与needle比较,一样则返回idx
        idx = 0
        while idx < hay_len - nee_len + 1:
            if haystack[idx:idx+nee_len] == needle:
                return idx
            idx += 1
        # 如果一直没找到,则返回-1
        return -1

常规思路,循环比对。利用python的切片操作。如果是其他语言,可以使用双指针方法。

 

 

 

###

posted @ 2020-02-25 10:44  风间悠香  阅读(409)  评论(0编辑  收藏  举报