300分钟搞定算法面试之常用数据结构

       很多时候看到题目,如果你认真的思考,大都会得到一个直观的思路,跟着这个思路做会得到一个结果。但这只是第一步,很多时候first thought的结果并非最优的,甚至会有很多冗余。这个时候我们不能满足于仅仅跑通代码,得到一个预期的输出就ok了。否则我们很难得到有效的进步。我认为我们要再次审题,再次审视自己的代码,看看哪些地方的计算有冗余,可以优化,看看哪些概念可以转换,很多时候你换个角度理解,就会发现不同的角度竟然可以更高效的解决相同的问题。 

     每道题目都会有多种解法,希望自己在练习时都能深入去思考,思考问题的本质在哪里。当然前提是打好基本功。

好了,唠完开始上题。以下很多题目来自leetcode

常用的数据结构 包括 数组、字符串、链表、队列、栈、树。下面会逐一回顾和举例。

一、数组

用于按顺序存储元素的集合。元素可以随机存取,因为数组中元素可通过索引访问。存取的时间复杂度为O(1)

数组可以是一维,也可以是多维的。 一维数组也称为线性数组。

以下代码简单说明数组的基本用法

1、数组定义,初始化

int[] a0 = new int[5];

int[] a1 = {1,2,3};

以上为两种初始化方法。

2、数组访问

for(int i=0;i

     System.out.println( a1[i] )

以上数组的定义都是固定容量,即初始化时指定了数组大小。这种形式有时很不方便。

因此大多数语言都提供了内置的动态数组,它仍是随机存取的列表数据结构,但大小是可变的。

如c++ 中的vector java中的ArrayList C#中的List<> 以及python中的列表list

以下是一些数组相关的代码编程题,皆来自leetcode 

题一 

给定一个整数类型的数组 nums,请编写一个能够返回数组“中心索引”的方法。

我们是这样定义数组中心索引的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。

如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个。

例如:

输入: 
nums = [1, 7, 3, 6, 5, 6]
输出: 3  解释 :索引3 (nums[3] = 6) 的左侧数之和(1 + 7 + 3 = 11),与右侧数之和(5 + 6 = 11)相等。
同时, 3 也是第一个符合要求的中心索引。

为了练习python,以下全部用python语言编写

   方法一 暴力破解, 遍历所有情况 ,时间复杂度O(n*n)
    def pivotIndex(nums):  
        if nums == None:
             return -1
         for i in range(len(nums)):
             left,right =0,0
             for j in range(i):
                 left += int(nums[j])
             for k in range(i+1,len(nums)):
                 right += int(nums[k])
             if left == right:
                 return i

         return -1
    
        #方法一可以解决问题,但显然效率太低,计算有冗余,我们要想办法减少计算冗余,提升算法效率
        #方法二 遍历优化,由于每次计算左边的和,或者右边的和时,都要进行累加,仔细思考后发现这其中的重复计算在于,当pivot向右移动时,其实不用每次都全部元素累加,只需保存上次的累加和,左边在上次的基础上加上新的值,而右边只需在上次的基础上减去一个值就可以了。

   def pivotIndex(nums):  
        llast,rlast = 0,sum(nums[1:])
        for i in range(len(nums)):
            if llast == rlast:
                return i
            if i == len(nums)-1:
                return -1
            llast += nums[i]
            rlast -= nums[i+1]
        return -1
        #方法三,进一步优化程序的编写,上述代码略微繁琐
    def pivotIndex(nums):
        left = 0
        total = sum(nums)
        for i,j in enumerate(nums):
            if left == (total - j )/2:
                return i
            left += j
        return -1

上述方法,一个是注意enumerate()的用法,迭代时,前面的 i 代表索引,j 代表数组中的值

 

题目二:至少是其他数字两倍的最大数

在一个给定的数组nums中,总是存在一个最大元素 。

查找数组中的最大元素是否至少是数组中每个其他数字的两倍。

如果是,则返回最大元素的索引,否则返回-1。

#方法一,也是最朴素的思路,首先找到这个最大值和其索引,然后遍历数组,看是否满足条件
def dominantIndex(nums):
     maxval,index = 0,-1
     for i,j in enumerate(nums):
         if maxval < j:
                 maxval = j
                 index = i
     for i,j in enumerate(nums):
          if index == i:
                 continue
          if maxval < 2*j:
                 return -1
     return index

#方法二 ,借助排序,也是一个常用的技巧。排序后,将最大的元素与第二大的元素进行比较,只要一次即可
if len(nums) == 1:
       return 0
nums1 = sorted(nums,reverse=True)
if nums1[0] >= 2*nums1[1]:
      return nums.index(nums1[0])
return -1

这里也有几个算法之外的注意点:1是 sort() 与 sorted()的区别。sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。sort直接修改list本身,没有返回值,sorted()不修改要排序的可迭代对象,而是返回一个排序好的新的可迭代对象。

题目三:加一

给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头

实例:

输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。
#逻辑其实很简单,就是从低位加1,若和等于10,向高位进一位,继续计算,否则返回该数组
def plusOne(digits):
    #首先一个点,是倒叙遍历,这有点不太符合我们的常规操作,但原理是一样的
    for i in range(len(digits)-1,-1,-1):
        if digits[i] <= 8:
            digits[i] += 1
            return digits
        else:
            digits[i] = 0
    return [1]+digits

倒叙遍历,也可以通过 reversed 方法或 列表的reverse()方法倒排

二维数组类似,也可以用一维数组的形式来表示,常见例题包括螺旋矩阵、杨辉三角等。

 

二、字符串

字符串实际上是一个 unicode 字符数组。你可以执行几乎所有我们在数组中使用的操作。

有些语言中字符串是不可修改的,哪怕只是修改其中一个字符,也只能创建一个新的字符串。像python java都是这样。

python 可使用 replace('a','b')会返回一个新的字符串,也可先把字符串转换为可修改的字符数组或者是字符列表。修改后,再转换为字符串。python中可以使用join()函数

题目一:二进制求和

给定两个二进制字符串,返回他们的和(用二进制表示)。

输入为非空字符串且只包含数字 1 和 0

示例 :

输入: a = "11", b = "1"
输出: "100"
def addBinary(a,b):
    return bin(int(a,2)+int(b,2))[2:]
这里主要是记住两个python内置函数 int()它不单单是强制把参数转换为int类型,还可以接受第二参数,代表要转换的数是多少进制。向本题int(a,2)就是把a当做是2进制数,转换为10进制的整型。最后通过bin 把10进制数再转换为2进制。

题目二: 实现 strStr()函数

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

示例 :

输入: haystack = "hello", needle = "ll"
输出: 2
def strStr(self, haystack: str, needle: str) -> int:
        #方法一,直接调用api
        #return haystack.find(needle)
        
        #方法二,遍历
        len1 = len(haystack)
        len2 = len(needle)
        if len1 < len2:
            return -1
        start,end = 0,len2-1
        while end < len1:
            substr = haystack[start:end+1]
            if substr == needle:
                return start
            start += 1
            end += 1
        return -1

题目三:最长公共前缀

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

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

示例 1:

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

题目三:最长公共前缀

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

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

示例 1:

输入: ["flower","flow","flight"]
输出: "fl"
 def longestCommonPrefix(strs: List[str]) -> str:
        if len(strs) == 0:
            return ""
        result = []
        index = 0
        minLen = 9999
        for item in strs:
            if len(item) < minLen:
                minLen = len(item)
        while index < minLen:
            for item in strs:
                if strs[0][index] != item[index]:
                    return "".join(result)
            result.append(strs[0][index])
            index += 1
        return "".join(result)

思想很朴素,先得到最短的字符串长度,逐个索引位置 遍历每个字符串,如果全部相等,该位置字符放入列表,若不相等直接返回列表对应的字符串

题目四:翻转字符串里的单词

给定一个字符串,逐个翻转字符串中的每个单词。

 

示例 1:

输入: "the sky is blue"
输出: "blue is sky the"
def reverseWords(s: str):
    return ' '.join(s.split()[::-1])

这里要注意,不需要指定split()的参数,我之前老是用split(' ')以为这样按空格分隔的来分裂,但这样遇到多个空格时会把空格拆成独立的元素。 而不加参数时,默认为所有的空字符,包括空格、换行(\n)、制表符(\t)等分隔。所以这里根本不需要加参数。

题目五:反转字符串中的单词 III

给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

示例 1:

输入: "Let's take LeetCode contest"
输出: "s'teL ekat edoCteeL tsetnoc" 
def reverseWords(s: str) -> str:
        li = s.split()
        return ' '.join(map(lambda x:x[::-1],li))

题目六:移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
 #解法一,快慢指针
def moveZeroes(nums) -> None:
        slow = 0
        for fast in range(len(nums)):
            if nums[fast] != 0:
                nums[slow], nums[fast] = nums[fast], nums[slow]
                slow += 1
def MoveZero(nums):
    for num in nums:
        if num == 0:
            nums.remove(0) #remove方法删除列表中第一个0元素
            nums.append(0) #末尾加一个0

上面是两种常用的解题方法,方法一是经典的快慢指针,快指针寻找到非0的元素就与慢指针交换元素值。

方法二,在循环中删除和添加,有些语言是不支持的,这点python的设计很神奇

题目 七 :有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false
 def isAnagram(self, s: str, t: str) -> bool:
        if len(s) != len(t):
            return False
        dic1,dic2 = {},{}
        for item in s:
            if item not in dic1:
                dic1[item] = 1
            else:
                dic1[item] += 1
          
        for item in t:
            if item not in dic2:
                dic2[item] = 1
            else:
                dic2[item] += 1
        
        if dic1 == dic2:
           return True
        else:
            return False

其实有很多种解法,上面这种是借助字典。

242 数组 字符串 

 

24/25链表

你可能感兴趣的:(算法专栏)