二刷剑指offer(下)——40-66题

41 输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

     if not array:
            return []
        i = 0
        j = len(array) - 1
        num = 0
        while i < j:
            if array[i] + array[j] == tsum:
                return  array[i],array[j]
            else:
                if array[i] + array[j] > tsum:
                    j = j-1
                else:
                    i = i+1
        return []

思路:双指针法,头尾各一个指针,当值大于tsum后指针前移,反之前指针后移,若相遇则退出

易错点: 注意指针移动的条件,由于是递增的数组因此最先找到的一定是乘积最小的,注意返回的是[],不是None

42 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

        if not s:
            return ''
        return s[n:]+s[:n]

思路1:利用python中的字符串切片函数解决

 

        res1 = self.strreverse(s[0:n]) #三次反转解决,注意字符串反转的几种方法,但是reverse只能直接对List使用
        res2 = self.strreverse(s[n:])
        res = res1 + res2
        res = self.strreverse(res)
        return res
    def strreverse(self,temp):
        return temp[::-1]

思路2:剑指offer的思路,利用三次反转来解决。

43 牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

        res = s.split(' ')
        res.reverse()
        res = ' '.join(res)
        return res
        

思路1:利用分割与合并函数

注意:str与List的转换方法

# -*- coding:utf-8 -*-
class Solution:
    def ReverseSentence(self, s):
        res = self.resverseone(s)
        res = res.split(' ')
        res1 = []
        for i in res:
            i = self.resverseone(i)
            res1.append(i)
        return ' '.join(res1)
        
        
    def resverseone(self,s):
        temp = []
        news = ''
        for i in s:
            temp.append(i)
        for i in s:#重要
            news = news+temp.pop()
        return news
            

思路2:剑指offer思路,先反转整体,再逐个反转

易错点:!反转函数的第二次循环,不能写成in temp。因为在pop的时候,temp的长度一直在改变。可以写成 range(len(temp))

44 LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

# -*- coding:utf-8 -*-
class Solution:
    def IsContinuous(self, numbers):
        # write code here

        if not numbers:
            return False
        numbers.sort()
        num0 = numbers.count(0)
        if num0 == 0:
            temp = set(numbers)
            if max(temp)-min(temp) == 4 and len(temp)==5:
                return True
            else:
                return False
        elif num0 == 1:
            numbers.remove(0)
            temp = set(numbers)
              
            if max(temp)-min(temp) <= 4 and len(temp)==4:
                return True
            else:
                return False
        elif num0 == 2:  
            print(1)
            temp = set(numbers)
            temp.remove(0)
            if max(temp)-min(temp) <= 4 and len(temp)==3:
                return True
            else:
                return False
        elif num0 ==3:  
            print(1)
            temp = set(numbers)
            temp.remove(0)
            if max(temp)-min(temp) <= 4 and len(temp)==2:
                return True
            else:
                return False
        elif num0 ==4:
            return True

思路:根据0的数量分类讨论

注意点:存在0的时候先remove,利用set去重,题目中有四个大小王

链接:https://www.nowcoder.com/questionTerminal/762836f4d43d43ca9deb273b3de8e1f4
来源:牛客网

1、如果输入为空,返回false
2、除了王的任何某个特定数值的牌出现两张或者更多,那么一定凑不齐顺子。
思路,先统计王的数量,再把牌排序,如果后面一个数比前面一个数大于1以上,那么中间的差值就必须用王来补了。看王的数量够不够,如果够就返回true,否则返回false。


def IsContinuous(self, numbers):
 
    if not numbers:return False
    numbers.sort()
    zeroNum = numbers.count(0)
    for i, v in enumerate(numbers[:-1]):
        if v != 0:
            if numbers[i+1]==v:return False
            zeroNum = zeroNum - (numbers[i + 1] - v) + 1
            if zeroNum < 0:
                return False
    return True

思路2:来自评论区,见注释。

思路3:1. 除0外没有重复的数   2. max - min < 5

45 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

(即是著名的约瑟夫环问题

        if n < 1 :
            return -1  #异常输入
        start = 0   #每次的开头
        con = range(n) 
        while con:
            final = (start + m -1 ) %n #关键,确定本轮的位置
            res = con.pop(final)     #res记录每次弹出的值,最后更新为最后的值
            n = n - 1   #长度变小
            start = final # 弹出后,从此处开始,由于前一个弹出这时的final的相对位置,已经往后一位了
        return res

思路:每次都计算一次本轮位置。模拟这个过程。

递归解法:
        if n == 0:
            return -1
        if n == 1:
            return 0
        else:
            return ((self.LastRemaining_Solution(n-1,m)+m)%n)
通过率:83.3%,超过递归深度
循环解法:
        if n < 1:
            return -1
        last = 0
        i = 2
        while  i<=n:
            last = (last+m)%i
            i += 1
        return last

 

思路2:找出递推公式,

f[1]=0;

f[i]=(f[i-1]+m)%i;  (i>1)

具体参考见 https://blog.csdn.net/u011500062/article/details/72855826 超清晰。

转载:理解这个递推式的核心在于关注胜利者的下标位置是怎么变的。每杀掉一个人,其实就是把这个数组向前移动了M位。然后逆过来,就可以得到这个递推式。因为求出的结果是数组中的下标,最终的编号还要加1

注意7的下标变化。

构造链表 https://blog.csdn.net/hj7jay/article/details/79956867

递归理解:https://blog.csdn.net/yanweibujian/article/details/50876631,也很清晰

46 求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

# -*- coding:utf-8 -*-
class Solution:
    def Sum_Solution(self, n):
        # write code here 短路求值
        ans = n
        temp = ans and self.Sum_Solution(n-1)
        ans = ans + temp
        return ans

思路:短路求值&&就是逻辑与,逻辑与有个短路特点,前面为假,后面不计算。利用了递归。

等同于:

        if n == 0:
            return 0
        ans = n
        ans = ans + self.Sum_Solution(n-1)
        return ans

上边是正常的递归条件。本题中用短路求值代替了判断n==0的情形。

47 写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

# -*- coding:utf-8 -*-
class Solution: 
    def Add(self, a, b):      #越界检查,正负数判断     
        while(b):  #有进位的时候
            a,b = (a^b) & 0xFFFFFFFF,((a&b)<<1) & 0xFFFFFFFF #a为直接加不考虑进位,b为进位,知道没有进位结束
        return a if a<=0x7FFFFFFF else ~(a^0xFFFFFFFF)
        

思路:分三步

a.先求直接加不考虑进位,二进制中用异或运算

b.再求进制,通过求与运算之后左移一位

c.两者相加,有进位再循环

易错点:python没有无符号右移操作,需要越界检查,详细解释https://blog.csdn.net/lrs1353281004/article/details/87192205

48 将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。

        if not s:
            return 0
        if s[0] not in ['0','1','2','3','4','5','6','7','8','9','+','-']: #判断首字符
            return 0
        for i in s[1:]:
            if i not in ['0','1','2','3','4','5','6','7','8','9']: #判断后面字符
                return 0
        res = 0                                                #分情况讨论
        if  s[0] =='+':
            length = len(s)-2
            j = 0
            for i in s[1:]:
                res = self.findnum(i)*10**(length-j)+res    
                j = j+1
            return res
                
        elif s[0] == '-':
            length = len(s)-2
            j = 0
            for i in s[1:]:
                res = self.findnum(i)*10**(length-j)+res
                j = j+1
            return -1*res
        else:
            length = len(s)-1
            j = 0
            for i in s:
                res = self.findnum(i)*10**(length-j)+res
                j = j+1
            return res
    def findnum(self,n):                              #用索引的形式返回其对应的整数值
        res = ['0','1','2','3','4','5','6','7','8','9']
        return res.index(n)
            
        

思路:先确定字符串合法化,随后通过索引的形式判断每个字符对应的整数,最后进行运算即可。

易错点:

摘自评论区:

边界条件:

数据上下 溢出

空字符串

只有正负号

有无正负号

错误标志输出

49 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

    if not numbers:
        return False
    res = []
    for i in numbers:
        if i not in res:
            res.append(i)
        else:
            duplication[0] = i
            return True
    return False

思路:建立一个链表,如果不在里面,添加,若在,返回

易错点:注意本题目中对输出的要求。

    def duplicate(self, numbers, duplication):
        if not numbers:
            return False
        for i,j in enumerate(numbers):
            while numbers[i] != i:
                if numbers[i] ==  numbers[numbers[i]]:
                    duplication[0] = numbers[i]
                    return True
                temp = numbers[i]
                numbers[i] = numbers[temp]
                numbers[temp] = temp
        return False

思路2:书上的思路,可以使空间复杂度为O(1),但是改变了原数组。链接:https://www.nowcoder.com/questionTerminal/623a5ac0ea5b4e5f95552655361ae0a8
来源:牛客网

1、判断输入数组有无元素非法

2、从头扫到尾,只要当前元素值与下标不同,就做一次判断,numbers[i]与numbers[numbers[i]],相等就认为找到了

重复元素,返回true,否则就交换两者,继续循环。直到最后还没找到认为没找到重复元素,返回false

*/

思路3:类似于二分查找,可以不改变原数组。以时间换空间。

50 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法

    def multiply(self, A):
        B = []
        for n,i in enumerate(A):
            res = 1
            for j in A[:n]+A[n+1:]:
                res = res*j
            B.append(res)
        return B

思路1:暴力法,按公式实现

易错点: A[:n]+A[n+1:],而不是 A[:n]+A[n:]

    def multiply(self, A):
        # write code here
        #计算上下两个三角,然后相乘
        length = len(A)
        B = list(range(length))  #初始化链表
        if length:           #不要写成while
            B[0] = 1   
            for i in range(1,length): #下三角
                B[i] = B[i - 1] * A[i -1]
            temp = 1
            for j in range(length-2,-1,-1):  #range的用法很重要
                temp = temp  * A[j+1]   #上三角
                B[j] = temp * B[j]       #上下三角相乘
            return B

思路2:上下三角法。如图。

       

 51 请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配   


# -*- coding:utf-8 -*-
'''
题目:请实现一个函数用来匹配包括'.'和'*'的正则表达式。
模式中的字符'.'表示任意一个字符(不包括空字符!),而'*'表示它前面的字符可以出现任意次(包含0次)。 
在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配
'''
class Solution:
    # s, pattern都是字符串
    def match(self, s, pattern):
        # 如果s与pattern都为空,则True
        if len(s) == 0 and len(pattern) == 0:
            return True
        # 如果s不为空,而pattern为空,则False
        elif len(s) != 0 and len(pattern) == 0:
            return False
        # 如果s为空,而pattern不为空,则需要判断
        elif len(s) == 0 and len(pattern) != 0:
            # pattern中的第二个字符为*,则pattern后移两位继续比较
            if len(pattern) > 1 and pattern[1] == '*':
                return self.match(s, pattern[2:])
            else:
                return False
        # s与pattern都不为空的情况
        else:
            # pattern的第二个字符为*的情况
            if len(pattern) > 1 and pattern[1] == '*':
                # s与pattern的第一个元素不同,则s不变,pattern后移两位,相当于pattern前两位当成空
                if s[0] != pattern[0] and pattern[0] != '.':
                    return self.match(s, pattern[2:])
                else:
                    # 如果s[0]与pattern[0]相同,且pattern[1]为*,这个时候有三种情况
                    # pattern后移2个,s不变;相当于把pattern前两位当成空,匹配后面的
                    # pattern后移2个,s后移1个;相当于pattern前两位与s[0]匹配
                    # pattern不变,s后移1个;相当于pattern前两位,与s中的多位进行匹配,因为*可以匹配多位
                    return self.match(s, pattern[2:]) or self.match(s[1:], pattern[2:]) or self.match(s[1:], pattern)
            # pattern第二个字符不为*的情况
            else:
                if s[0] == pattern[0] or pattern[0] == '.':
                    return self.match(s[1:], pattern[1:])

   思路:代码来自评论区。重点是对于*的讨论。详情见注释。 

                                  

52 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。

# -*- coding:utf-8 -*-
class Solution:
    # s字符串
    def isNumeric(self, s):
        # write code here
        #标记小数点,符号,E是否出现过
        sign = False
        decimal = False
        hasE = False
        for i in range(len(s)): #遍历字符串
            if s[i] =='e' or s[i] == 'E': #遇到E的情况
                if i == len(s) - 1: return False #E不能在最后一位
                if hasE: return False#E不能有第二次
                hasE = True #将E的标志改变
            elif s[i] =='+' or s[i] =='-': #出现符号的情况
                if sign and s[i-1] != 'e' and s[i-1] !='E': return False #存在第二个符号而且前面不是E
                if not sign and i > 0 and s[i-1] != 'e' and s[i-1] !='E' :#不在第一位出现符号且前面不是E
                    return False
                sign = True
            elif s[i] =='.': #点只能一次且不能在E之后
                if hasE or decimal:
                    return False
                decimal = True
            elif s[i] < '0' or s[i] > '9': #判断有无特殊字符
                return False
        return True

思路:重点关注几个特殊符号,设定标志位,然后按情况分别判断。

参考:

链接:https://www.nowcoder.com/questionTerminal/6f8c901d091949a5837e24bb82a731f2
来源:牛客网

class Solution {
public:
    bool isNumeric(char* str) {
        // 标记符号、小数点、e是否出现过
        bool sign = false, decimal = false, hasE = false;
        for (int i = 0; i < strlen(str); i++) {
            if (str[i] == 'e' || str[i] == 'E') {
                if (i == strlen(str)-1) return false; // e后面一定要接数字
                if (hasE) return false;  // 不能同时存在两个e
                hasE = true;
            } else if (str[i] == '+' || str[i] == '-') {
                // 第二次出现+-符号,则必须紧接在e之后
                if (sign && str[i-1] != 'e' && str[i-1] != 'E') return false;
                // 第一次出现+-符号,且不是在字符串开头,则也必须紧接在e之后
                if (!sign && i > 0 && str[i-1] != 'e' && str[i-1] != 'E') return false;
                sign = true;
            } else if (str[i] == '.') {
              // e后面不能接小数点,小数点不能出现两次
                if (hasE || decimal) return false;
                decimal = true;
            } else if (str[i] < '0' || str[i] > '9') // 不合法字符
                return false;
        }
        return true;
    }
};

53 请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

# -*- coding:utf-8 -*-
'''
题目:请实现一个函数用来找出字符流中第一个只出现一次的字符。
例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。
当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
'''
class Solution:
    def __init__(self):
        self.char_list = [-1 for i in range(256)] #定义一个数组,初始化-1
        self.index = 0  # 记录当前字符的个数,可以理解为输入的字符串中的下标
    '''
    解法:利用一个int型数组表示256个字符,这个数组初值置为-1.
    每读出一个字符,将该字符的位置存入字符对应数组下标中。
    若值为-1标识第一次读入,不为-1且>0表示不是第一次读入,将值改为-2.
    之后在数组中找到>0的最小值,该数组下标对应的字符为所求。
    在python中,ord(char)是得到char对应的ASCII码;chr(idx)是得到ASCII位idx的字符
    '''
    def FirstAppearingOnce(self):
        # write code here
        min_value = 500
        min_idx = -1
        for i in range(256):#遍历这个数组
            if self.char_list[i] > -1: #如果当前值不是负数,则肯定是第一次更改过的
                if self.char_list[i] < min_value: #这一步为了找到最小的值,也就是最小的索引。即第一个出现的。
                    min_value = self.char_list[i] #每次都更新最小值
                    min_idx = i  #记录i
        if min_idx > -1: #如果min_idx被更改过
            return chr(min_idx) #chr(idx)是得到ASCII位idx的字符
        else:
            return '#'
    def Insert(self, char):
        # 如果是第一出现,则将对应元素的值改为下边
        if self.char_list[ord(char)] == -1:
            self.char_list[ord(char)] = self.index #将字符流中的位置保存在容器
        # 如果出现过一次,则进行修改,修改为-2
        else:  #第二次或以上次数出现,将其位置换成-2
            self.char_list[ord(char)] = -2
        self.index += 1 #字符流位置加1

或写成:

# -*- coding:utf-8 -*-
'''
题目:请实现一个函数用来找出字符流中第一个只出现一次的字符。
例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。
当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
'''
class Solution:
    def __init__(self):
        self.char_list = [-1 for i in range(256)] #定义一个数组,初始化-1
        self.index = 0  # 记录当前字符的个数,可以理解为输入的字符串中的下标
    '''
    解法:利用一个int型数组表示256个字符,这个数组初值置为-1.
    每读出一个字符,将该字符的位置存入字符对应数组下标中。
    若值为-1标识第一次读入,不为-1且>0表示不是第一次读入,将值改为-2.
    之后在数组中找到>0的最小值,该数组下标对应的字符为所求。
    在python中,ord(char)是得到char对应的ASCII码;chr(idx)是得到ASCII位idx的字符
    '''
    def FirstAppearingOnce(self):
        # write code here
        res = []
        for i in range(256):#遍历这个数组
            if self.char_list[i] > -1: #如果当前值不是负数,则肯定是第一次更改过的
                res.append(self.char_list[i])      
        if res: #如果min_idx被更改过
            return chr(self.char_list.index(min(res))) #找出索引最小的值所对应的i值,即ascii码
        else:
            return '#'
    def Insert(self, char):
        # 如果是第一出现,则将对应元素的值改为下边
        if self.char_list[ord(char)] == -1:
            self.char_list[ord(char)] = self.index #将字符流中的位置保存在容器
        # 如果出现过一次,则进行修改,修改为-2
        else:  #第二次或以上次数出现,将其位置换成-2
            self.char_list[ord(char)] = -2
        self.index += 1 #字符流位置加1

思路:每次读取一个字符,将其在位置index保存在下标为其ascii码的数组里。当再次读取,发现某ascii对应的数组位不是初始值,则将其改成-2。在查找最小值的时候,找出数组的不为-1或者-2的值,即出现过一次的值,再找其对应的index最小,即最先出现,即数组中值最小的,然后就可以根据其数组下标,求得其对应的值了。

易错点:搞清楚建立的数组,下标与其中的值分别对应的是什么,题目就不难了。

54 给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

    def EntryNodeOfLoop(self, pHead):
        if not pHead:
            return None
        res = []
        p1 = pHead
        while p1 not in res:
            res.append(p1)
            if p1.next:  #注意没用环的情况
                p1 = p1.next
            else:
                return None
        return p1

思路:用一个列表记录出现过的结点,如果有重复,则返回这个值

易错点:注意没环的情况,即p1.next不一定存在

    def EntryNodeOfLoop(self, pHead):
        if not pHead:
            return None
        fast = pHead
        low = pHead  #快慢结点
        clength = 1  #记录环的长度
        while fast.next: #当fast没走到头时候
            fast = fast.next.next  #走两步
            low = low.next
            if fast == low:   
                lownew = low.next  #计算环的长度
                while low != lownew:
                    clength += 1
                    lownew = lownew.next
                p1 = pHead
                p2 = pHead  
                for i in range(clength):
                    p2 = p2.next
                while p1 != p2:
                    p1 = p1.next
                    p2 = p2.next
                return p1
        return None

思路2:来自剑指offer书,先让一快一慢指针走,相遇说明存在环。之后原地行进,绕一圈记录环的长度。最后指针P2在链表上向前移动n步,然后两个指针以相同的速度向前移动。相遇即为环节点。

易错点:注意环长度的计算部分。

55 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

    def deleteDuplication(self, pHead):
        if not pHead:  #异常输入判断
            return None
        pre = ListNode(-1) #头节点的前一个节点
        pre.next = pHead
        first = pre   #备份这个节点
        now = pHead  #当前节点
        while now.next: #当未到最后一个结点
            if now.val == now.next.val: #如果相等
                while now.next and now.val == now.next.val: #相等则一直往后
                    now = now.next
                pre.next = now.next #前一个节点指向重复节点最后一位的后一个节点
                now = pre  #将工作节点从重复节点的最后一位设定为前一位
            pre = now #将前一位设置为当前节点
            if now.next: #如果不是最后一位,则当前节点设定为
                now = now.next
        return first.next #返回备份节点的后一个节点

思路:使前一个节点始终指向重复节点的后一个节点,

注意点:要定义一个头节点前的节点,因为头节点也可能重复;在删除完重复后,记得把now前移到不重复的节点。

同思路代码:

    def deleteDuplication(self, pHead):
        first = ListNode(-1)
        first.next = pHead
        pre = first
        cur = pHead
        while cur != None:
            Dup = False
            #while cur.val == cur.next.val and cur.next != None:
            while cur.next != None and cur.val == cur.next.val: #先后顺序!!
                Dup = True
                cur = cur.next
            if not Dup:
                pre = cur
                cur = cur.next
            else:
                pre.next = cur.next
                cur = cur.next #dont forget
        return first.next

56 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

 #找规律,分三种情况讨论(评论区)
    def GetNext(self, pNode):
        if not pNode:
            return None
        if pNode.right:
            pNode = pNode.right
            while pNode.left:
                pNode = pNode.left
            return pNode
        while pNode.next:
            if pNode.next.left == pNode:
                return pNode.next
            pNode = pNode.next
        

思路1:书上解法。链接:https://www.nowcoder.com/questionTerminal/9023a0c988684a53960365b889ceaf5e
来源:牛客网

分析二叉树的下一个节点,一共有以下情况:

1.二叉树为空,则返回空;

2.节点右孩子存在,则设置一个指针从该节点的右孩子出发,一直沿着指向左子结点的指针找到的叶子节点即为下一个节点;

3.节点不是根节点。如果该节点是其父节点的左孩子,则返回父节点;否则继续向上遍历其父节点的父节点,重复之前的判断,返回结果。如图。

    def GetNext(self, pNode):
        if not pNode:
            return None
        proot = pNode
        while proot.next:
            proot = proot.next
        self.res = []
        self.mid(proot)
        index = self.res.index(pNode)
        if index+1 < len(self.res):
            return self.res[index+1]
        else:
            return None
    def mid(self,proot):
        if not proot:  #基准条件
            return
        self.mid(proot.left)
        self.res.append(proot) #添加的是结点不是值
        self.mid(proot.right)
    

思路2:

链接:https://www.nowcoder.com/questionTerminal/9023a0c988684a53960365b889ceaf5e
来源:牛客网
思路,如果这道题是求中序遍历,肯定很简单。所以我们先写一个中序遍历的算法。关键是从根节点开始遍历,所以第一步还是找到某个节点的根节点,方法是一直使用next判断即可。再将从根节点中序遍历的结果保存到一个数组中,直接找pNode所在索引的下一个即可。当然要考虑这个节点是不是最后一个,如果是最后一个,直接返回None。

易错点:注意先判断index+1是不是大于数组范围了。不能判断res[index+1]是否存在,这样本身就不合法。另外要注意添加的是节点不是val。中序遍历要有return即基准条件。

57 请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

    def isSymmetrical(self, pRoot):
        if not pRoot:
            return True
        return self.compare(pRoot.left,pRoot.right)
    def compare(self,left,right):
        if not left and not right:
            return True
        if not left or not right:
            return False
        if left.val == right.val:
            if self.compare(left.left,right.right) and self.compare(left.right,right.left):
                return True
        return False
        

思路: (评论区)

/*思路:首先根节点以及其左右子树,左子树的左子树和右子树的右子树相同

* 左子树的右子树和右子树的左子树相同即可,采用递归

* 非递归也可,采用栈或队列存取各级子树根节点

*/Python 简单解法,判断左右子节点的值是否相同即可,并通过递归左右节点遍历整个树

剑指offer思路:

代码很简单,关键还是知道怎么样才能判断一个

二叉树是否对称,只要采用前序、中序、后序、层次遍历等任何一种遍历方法,分为先左后右和先

右后左两种方法,只要两次结果相等就说明这棵树是一颗对称二叉树。

58 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

        if not pRoot: #异常输入处理
            return []
        result = []   #存储输出值
        node = [pRoot] #当前处理的行结点
        while node:
            newnode = []
            res = []
            for i in node:
                res.append(i.val)
                if i.left:
                    newnode.append(i.left)
                if i.right:
                    newnode.append(i.right)
            result.append(res)
            node = newnode
        final = []
        for j,i in enumerate(result): #偶数行反转
            if j%2 ==1:
                i.reverse()
            final.append(i)
        return final

思路:层序遍历的思路,每次把下一行存入新列表,之后依次遍历。注意最后要反转偶数行。

        if not pRoot:
            return []
        result = []
        node = [pRoot]
        i = 0
        while node:
            i = i + 1
            newnode = []
            res = []
            while node:
                if node[-1].val:
                    res.append(node[-1].val)
                    if i%2 == 1:
                        if node[-1].left: #判断
                            newnode.append(node[-1].left)
                        if node[-1].right:
                            newnode.append(node[-1].right)
                    else:
                        if node[-1].right:
                            newnode.append(node[-1].right)
                        if node[-1].left:
                            newnode.append(node[-1].left)
                node.pop()
            result.append(res)
            node = newnode
        return result
                

思路2:剑指offer书上的思路。利用栈来实现,注意要奇偶行。

易错点:先判断存在与否再添加,注意不分奇偶数行的话会报错。

59 从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行

    def Print(self, pRoot):
        if not pRoot:
            return []
        result = []
        node = [pRoot]
        while node:
            newnode= []
            res = []
            for i in node:
                if i.left:
                    newnode.append(i.left)
                if i.right:
                    newnode.append(i.right)
                if i.val:
                    res.append(i.val)
            node = newnode
            result.append(res)

思路:跟上一题一样

易错点:不要忘记异常输入的判断。

60 请实现两个函数,分别用来序列化和反序列化二叉树

class Solution:
    def __init__(self):
        self.flag = -1  #设定标志位
    def Serialize(self, root):
        # write code here
        s = ''
        self.res = []
        self.fro(root)
        s = ','.join(self.res)
        return s
    def fro(self,root):
        if not root:
            self.res.append('#')  #注意不能直接return #,注意双引号
            return                  
        self.res.append(str(root.val))  #注意str
        self.fro(root.left)  #递归
        self.fro(root.right) 
    def Deserialize(self, s):
        # write code here
        self.flag += 1             
        l = s.split(',')
        root = None #默认为空
        if l[self.flag] != '#':  #不为空
            root = TreeNode(int(l[self.flag])) #要Int
            root.left = self.Deserialize(s)  #跟前序遍历一样递归
            root.right = self.Deserialize(s)
        return root
            
        
                          
 #前序遍历可以直接找到根节点

思路:(评论区)

 1. 对于序列化:使用前序遍历,递归的将二叉树的值转化为字符,并且在每次二叉树的结点

不为空时,在转化val所得的字符之后添加一个' , '作为分割。对于空节点则以 '#' 代替。

 2. 对于反序列化:按照前序顺序,递归的使用字符串中的字符创建一个二叉树

注意反序列化时候,空直接返回None。

61 给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8)    中,按结点数值大小顺序第三小结点的值为4。

    def KthNode(self, pRoot, k):
        if not pRoot or not k: #加判断
            return None
        self.res = []
        self.mid(pRoot)
        if k > len(self.res): #重要,加判断
            return None
        return self.res[k-1]
    def mid(self,root):
        if not root:
            return None
        self.mid(root.left)
        self.res.append(root) #不是val,是root
        self.mid(root.right)
           

思路:将二叉树中序遍历存储在List,之后按需要提取。关键是理解二叉搜索树的中序遍历是递增的。

易错点:注意返回的是结点不是结点的值,注意异常值的处理,尤其是K超过res长度的时候。

63 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

class Solution:
    def __init__(self):
        self.res = []
    def Insert(self, num):
        self.res.append(num)
        self.res.sort()
    def GetMedian(self,s):
        length = len(self.res)
        if length%2 == 1:
            return self.res[length/2]
        else:
            return (self.res[length/2]+self.res[length/2 - 1])/2.0

思路:每插入就排序一次。

易错点:通过init定义类变量。引用要加self。、整数除以整数的结果为整数,如果想得到相除结果为
浮点数,则需要除以浮点数(来自https://blog.csdn.net/github_39611196/article/details/88919102 
self.l[length//2] + self.l[length//2 -1]) / 2 (这样会取整)
转换为
self.l[length//2] + self.l[length//2 -1]) / 2.0

https://www.nowcoder.com/questionTerminal/9be0172896bd43948f8a32fb954e1be1
# -*- coding:utf-8 -*-
from heapq import *
class Solution:
    def __init__(self):
        self.heaps = [], []
    def Insert(self, num):
     # write code here
        small, large = self.heaps
        heappush(small, -heappushpop(large, num))#将num放入大根堆,并弹出大根堆的最小值,取反,放入大根堆small
        if len(large) < len(small):
            heappush(large, -heappop(small)) #弹出small中最小的值,取反,即最大的值,放入large
    def GetMedian(self,ss):
     # write code here
        small,large = self.heaps
        if len(large) > len(small):
            return float(large[0])
        return (large[0] - small[0]) /2.0

思路2:用一个最大堆与一个最小堆实现。

链接:https://www.nowcoder.com/questionTerminal/9be0172896bd43948f8a32fb954e1be1
来源:牛客网
 

  • 大根堆:large保存大的半数的数据
  • 小根堆:small保存小的半数的数据
    获取中位数的时间复杂度为O(1),插入一个数据的时间复杂度为O(log(n))

技巧:

  1. 构造小根堆时,对数组元素取负值来构造
  2. heappush(heap,data),将deat放入大根堆中
  3. heapposh(heap),弹出heap中的最小值
  4. heappushpop(heap,data),将data放入大根堆中,再弹出堆heap的最小值

64 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

        if not num or not size or size > len(num):
            return []  #不是None
        res = []
        i = 0
        while i+size<=len(num):
            res.append(max(num[i:i+size]))
            i =i+1
        return res
        

思路1:暴力法,求每次的最大值。

class Solution:
    def maxInWindows(self, num, size):
        queue,res,i = [],[],0
        while size>0 and i<len(num):
            if len(queue)>0 and i-size+1 > queue[0]: #若最大值queue[0]位置过期 则弹出 , i-size+1 是最低位
                queue.pop(0)
            while len(queue)>0 and num[queue[-1]]<num[i]: #每次弹出所有比num[i]的数字
                queue.pop()
            queue.append(i)
            if i>=size-1: 
                res.append(num[queue[0]]) #q[0]是对应的下标,从第size-1开始
            i += 1
            temp = []
            for j in queue:
                temp.append(num[j])
            print(temp)
        return res

思路2;(评论区)双端队列法。其中queue保存的是数组的下标。

用一个双端队列,队列第一个位置保存当前窗口的最大值,当窗口滑动一次

1.判断当前最大值是否过期

2.新增加的值从队尾开始比较,把所有比他小的值丢掉

65 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

# -*- coding:utf-8 -*-
#回溯法
#遍历矩阵中的每一个位置
class Solution:
    def hasPath(self, matrix, rows, cols, path):
        # write code here
        if not matrix:
            return False
        if not path:
            return True
        x = [list(matrix[cols*i:cols*i+cols]) for i in range(rows)]
        for i in range(rows):
            for j in range(cols):
                if self.exist_helper(x, i, j, path):
                    return True
        return False
    def exist_helper(self, matrix, i, j, p):
        if matrix[i][j] == p[0]:
            if not p[1:]:
                return True #终点
            matrix[i][j] = '#' #已走标记
            if j > 0 and self.exist_helper(matrix, i, j-1, p[1:]):
                return True #重要,要有return,一层层传回去
            if j < len(matrix[0])-1 and self.exist_helper(matrix, i, j+1, p[1:]):
                return True
            if i > 0 and self.exist_helper(matrix, i-1, j, p[1:]):
                return True
            if i < len(matrix)-1 and self.exist_helper(matrix, i+1, j ,p[1:]):
                return True
            matrix[i][j] = p[0] #还原标记
            return False
        else:
            return False

思路:回溯法思想。将已走标记为#,如果不成立。则还原回去。

链接:https://www.nowcoder.com/questionTerminal/c61c6999eecb4b8f88a98f66b273a3cc
来源:牛客网

这是一个可以用回朔法解决的典型题。首先,在矩阵中任选一个格子作为路径的起点。如果路径上的第i个字符不是ch,那么这个格子不可能处在路径上的

第i个位置。如果路径上的第i个字符正好是ch,那么往相邻的格子寻找路径上的第i+1个字符。除在矩阵边界上的格子之外,其他格子都有4个相邻的格子。重复这个过程直到路径上的所有字符都在矩阵中找到相应的位置。

  由于回朔法的递归特性,路径可以被开成一个栈。当在矩阵中定位了路径中前n个字符的位置之后,在与第n个字符对应的格子的周围都没有找到第n+1个字符,这个时候只要在路径上回到第n-1个字符,重新定位第n个字符。

  由于路径不能重复进入矩阵的格子,还需要定义和字符矩阵大小一样的布尔值矩阵,用来标识路径是否已经进入每个格子。 当矩阵中坐标为(row,col)的格子和路径字符串中相应的字符一样时,从4个相邻的格子(row,col-1),(row-1,col),(row,col+1)以及(row+1,col)中去定位路径字符串中下一个字符

如果4个相邻的格子都没有匹配字符串中下一个的字符,表明当前路径字符串中字符在矩阵中的定位不正确,我们需要回到前一个,然后重新定位。 一直重复这个过程,直到路径字符串上所有字符都在矩阵中找到合适的位置

易错点:matrix的初始化方法。递归的终止条件。边界条件。标记与还原。

66 地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

# -*- coding:utf-8 -*-
class Solution:
    def __init__(self): #开始必备,注意self
        self.count = 0 #注意加self
    def movingCount(self,threshold,rows,cols):
        arr = [[1 for i in range(cols)] for j in range(rows)] #注意中括号
        self.findblocks(arr,0,0,threshold) #里面不加self,传入的顺序
        return self.count #self
    def findblocks(self,arr,i,j,k): #ijk对应的值
        if i < 0 or j < 0 or i >= len(arr) or j >= len(arr[0]):
            return
        sumi = list(map(int,str(i)))  #将数字转换为字符串再转换为列表进而用sum
        sumj = list(map(int,str(j)))
        if sum(sumi) + sum(sumj) > k or arr[i][j] != 1: #不符合条件或已走过(默认为1)
            return
        self.count = self.count + 1 #要加self,经历过前两关才可以加1
        arr[i][j] = 0 #走过的设置为0
        self.findblocks(arr,i,j-1,k)
        self.findblocks(arr,i,j+1,k)
        self.findblocks(arr,i-1,j,k)
        self.findblocks(arr,i+1,j,k)
#注意,一定要连通
#回溯,深度优先
    

思路:(评论区)

//思路:dfs,搜索四个方向,vis记录该方格是否被搜索过,

// 预判方格是否合法,合法就从该方格接着搜索

 

将地图全部置1,遍历能够到达的点,将遍历的点置0并令计数+1.这个思路在找前后左右相连的点很有用,比如leetcode中的海岛个数问题/最大海岛问题都可以用这种方法来求解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值