zoukankan      html  css  js  c++  java
  • [刷题] 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的切片操作。如果是其他语言,可以使用双指针方法。

    ###

  • 相关阅读:
    IDAPython学习(一)
    自动提取文件系统---binwalk(一)
    Scapy Fuzz实现——S7协议从建连到“正常交流“(一)
    工控系统的指纹识别技术
    揭秘VxWorks——直击物联网安全罩门
    VxWorks Fuzzing 之道:VxWorks 工控实时操作系统漏洞挖掘调试与利用揭秘
    路由器文件系统与提取
    python 调试技巧
    拿下id_rsa
    elasticsearch 7.1 401 Unauthorized
  • 原文地址:https://www.cnblogs.com/leokale-zz/p/12360459.html
Copyright © 2011-2022 走看看