Dongxing's Wiki Dongxing's Wiki
首页
  • 剑指 Offer
  • LeetCode
  • 算法与数据结构
  • Python 语言
  • Web 开发
  • Hive
  • Elastic Search
  • 机器学习
  • NLP
  • 检索技术
  • 数据分析
  • 经验笔记
  • Linux 配置
  • 博客进化记
  • 杂谈
GitHub (opens new window)
首页
  • 剑指 Offer
  • LeetCode
  • 算法与数据结构
  • Python 语言
  • Web 开发
  • Hive
  • Elastic Search
  • 机器学习
  • NLP
  • 检索技术
  • 数据分析
  • 经验笔记
  • Linux 配置
  • 博客进化记
  • 杂谈
GitHub (opens new window)
  • 剑指offer

    • 剑指 Offer - python 题解
    • 剑指Offer(1)
    • 剑指Offer(2)
    • 剑指Offer(3)
    • 剑指Offer(4)
    • 剑指Offer(5)
    • 剑指Offer(6)
    • 剑指Offer(7)
    • 剑指Offer(8)
    • 剑指Offer(9)
    • 剑指Offer(10)
    • 剑指Offer(11)
    • 剑指Offer(12)
    • 剑指Offer(13)
      • 把字符串转换成整数
        • 问题
        • 思路
        • 代码
      • 数组中重复的数字
        • 问题
        • 思路
        • 代码
      • 构建乘积数组
        • 问题
        • 思路
        • 代码
      • 正则表达式匹配
        • 问题
        • 思路
        • 代码
      • 表示数值的字符串
        • 问题
        • 思路
        • 代码
      • 第一个只出现一次的字符
        • 问题
        • 思路
        • 代码
      • 链表中环的入口结点
        • 问题
        • 思路
        • 代码
      • 删除链表中重复的结点
        • 问题
        • 思路
        • 代码
    • 剑指Offer(14)
    • 剑指Offer(15)
  • 剑指Offer
  • 剑指offer
anthony
2018-10-06
目录

剑指Offer(13)

更新:此题解 markdown 源文件已放到 Github https://github.com/dox1994/offer-coding-interviews-python (opens new window),欢迎前去给个小星星鸭!


# 把字符串转换成整数

# 问题

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

# 思路

逐个位来判断和累加计算就行,但需要注意各种各样的特殊情况要考虑周全。 比如,以+ - 开头的、字符串不能出现字母等其他符号、+-不能出现在中间,以及空字符串、只有+-字符的字符串等。

# 代码

# -*- coding:utf-8 -*-
class Solution:
    def StrToInt(self, s):
        # write code here
        minus = 1
        number = 0
        if not s:
            return number
        if s[0] == '+':
            if len(s) > 1:
                s = s[1:]
            else:
                return 0
        elif s[0] == '-':
            minus = -1
            if len(s) > 1:
                s = s[1:]
            else:
                return 0
        for i in range(0, len(s)):
            if '0' <= s[i] <= '9':
                number = number * 10 + ord(s[i]) - ord('0')
            else:
                return 0
        return number * minus
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 数组中重复的数字

# 问题

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

# 思路

基本的思路就是建一个hash表,读数先判断是否重复,不重复就放进去读下一个,直到找到第一个重复的。

另外可以进行排序,然后判断是否有相邻两个数重复。

但注意题目中条件,所有数字都在0到n-1范围内。因此无需开辟额外空间,可以直接在数组上做标记。读到数字时,将数组中该数字作为下标的位置的数字,减去n,作为标记。下次读到相同数字,去其下标位置,发现数字值小于0,说明已经被标记过,那么这个就是重复数字了。

一些细节:之所以不用加上n的方法来标记,是因为可能会导致数字过大溢出(减去n就不会,因为只要n不溢出, -n-1 就不溢出, -n-1+一个正数 就也不会溢出)。也不能乘以-1来做标记,因为这种方法对0就没法做标记了。

# 代码

# -*- coding:utf-8 -*-
class Solution:
    # 返回值格式:函数返回True/False,找到任意重复的一个值并赋值到duplication[0]
    def duplicate(self, numbers, duplication):
        length = len(numbers)
        for num in numbers:
            if num >= 0:
                # 直接按num为下标去找
                index = num
            else:
                # 如果此处已被标记,则下标要按此处的值 num+length 去找
                index = num + length
            if numbers[index] < 0:
                # 找到重复
                duplication[0] = numbers[index] + length
                return True
            else:
                # 不重复,则做下标记
                numbers[index] -= length
        return False
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 构建乘积数组

# 问题

给定一个数组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]。不能使用除法。

# 思路

如果可以使用除法,则先求出A所有元素的积,再依此对每个位置做除法就行(注意考虑到0的情况!)

让我们以一种清楚点的方式来看这个题,其实对B的每一个元素,都可以以下标i,把整个乘积分为前后两个部分:

具体到实现上,可以先做左下部分,即做一个循环,从上往下,B中当前元素的值,是A中对应的一个值和前一个B的值相乘得到。然后再对右上部分做一个循环,此时就要单独开辟一个temp来存储累乘值,从下往上把右上部分的值补全。

# 代码

# -*- coding:utf-8 -*-
class Solution:
    def multiply(self, A):
        if not A:
            return []
        B = [1] * len(A)
		# 先从上往下累乘左下半
        for i in range(1, len(A)):
			# 无需额外开辟temp存储累乘值,因为累乘值就是B[i-1]
            B[i] = B[i-1] * A[i-1]
		# 再从下往上累乘右上半
        temp = 1  # 需要额外开辟temp来存储累乘值了
        for i in range(len(A)-2, -1, -1):
            temp *= A[i+1]
            B[i] *= temp
        return B
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 正则表达式匹配

# 问题

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

# 思路

重点是要思路清晰吧……只能说很容易就忽略各种各样的情况,导致无法通过所有用例。

对初始输入的判断: 如果s和pattern都为空,则True;如果s不空,而pattern为空,则直接False.

然后来读取pattern中的每个字符:

  • 如果当前pattern字符的后面不是* ,则说明只可匹配1个
    • 如果当前pattern字符是.的话,可匹配1个任意字符
    • 如果不是点,则只能匹配给出的字符,若不匹配则直接返回
    • 完成后两者均往后+1,继续判断
  • 如果当前pattern字符的后面是* ,说明当前pattern字符可匹配0-任意多个。
    • 如果当前pattern字符与串的字符不一致,而且当前字符不是点,则当作是 * 的不匹配情况,pattern往后+2而字符串不变
    • 否则其他情况:
      • 要么共匹配0个,pattern往后+2,字符串不变
      • 要么共匹配1个,字符串往后+1, pattern 往后+2
      • 要么先匹配1个,留待继续匹配,此时字符串+1, pattern不变
      • 然后继续匹配后面的是否match;

# 代码

# -*- coding:utf-8 -*-
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:])
                else:
                    return False
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 表示数值的字符串

# 问题

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

# 思路

以下为剑指Offer上的思路: 表示数值的字符串遵循模式 A[.[B]][e|EC] 或者 .B[e|EC]。其中,A为数值的整数部分,B为小数部分,C为指数部分。要满足以下条件:

  • 可以没有A部分,或者没有B部分,但不能AB同时没有
  • AC均可能以+-开头的0-9字符
  • B也是0-9字符但不能有+-号

判断时,从左到右扫描字符串,先尽可能地扫描出整数部分A,碰到小数点再扫描得到B,碰到e或E再扫描得到C。

# 代码

按剑指 Offer 上的思路,来写代码如下:

# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.cursor = 0

    def isNumeric(self, s):
        if not s:
            return False
        # 先判断是否存在A部分
        numeric = self.scan_integer(s)
        # 如果出现小数点,则判断小数部分
        if (self.cursor < len(s)) and s[self.cursor] == '.':
            self.cursor += 1
            # 用or的原因是,既可以没有A部分,也可以没有B部分,也可以AB都有,但不能AB都没有
            numeric = self.scan_unsigned_integer(s) or numeric
        # 如果出现e,则判断指数部分
        if (self.cursor < len(s)) and (s[self.cursor] == 'e' or s[self.cursor] == 'E'):
            self.cursor += 1
            # 用and的原因是,既然出现了E,后面必须要有整数,且E的前面必须是已判断好了的合法数字
            numeric = numeric and self.scan_integer(s)
        
        return numeric and self.cursor == len(s)
            
        
    def scan_integer(self, s):
        # 作用:扫描字符串从当前cursor位置往后,是否有整数
        # 先处理掉前面的符号位(如果没有符号位就不处理,无影响)
        if (self.cursor < len(s)) and (s[self.cursor] == '+' or s[self.cursor] == '-'):
            self.cursor += 1
        # 再调用判断无符号整数的方法
        return self.scan_unsigned_integer(s)
    
    def scan_unsigned_integer(self, s):
        # 作用:扫描字符串从当前cursor位置往后,是否有无符号整数
        # 先记下cursor本来的位置
        original_cursor = self.cursor
        # 遇到 0-9 数字就往后移动cursor
        while (self.cursor < len(s)) and ('0' <= s[self.cursor] <= '9'):
            self.cursor += 1
        # 如果cursor比最初后移了,说明有无符号整数
        return self.cursor > original_cursor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

另有思路,用有限自动机来实现,这个感觉是挺好的,而且具有扎实的计算机基础,判断过程也会更严谨。 具体见 https://www.nowcoder.com/questionTerminal/6f8c901d091949a5837e24bb82a731f2

此外,再贴牛客上的一个实现思路:

# -*- coding:utf-8 -*-
class Solution:
    # s字符串
    def isNumeric(self, s):
        # 标记符号、小数点、e是否出现过
        sign = False
        decimal = False
        has_e = False
        for i in range(len(s)):
            if s[i] == 'e' or s[i] == 'E':
                if i == len(s) - 1:
                    return False  # E后面必须跟数字,E不可以在字符串结尾
                if has_e:
                    return False  # 不可以出现两个E
                has_e = True
            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'):
                    return False  # 如果第一次出现符号,且不是在开头出现,则必须紧跟在E之后
                sign = True
            elif s[i] == '.':
                if has_e or decimal:
                    return False  # E后面不能出现小数点,或小数点不能出现两次
                decimal = True
            elif s[i] < '0' or s[i] > '9':
                return False  # 不合法字符
        return True
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 第一个只出现一次的字符

# 问题

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

如果当前字符流没有存在出现一次的字符,返回#字符。

(注:本题就是求字符串中第一个只出现一次的字符,但是把具体代码实现稍微做了变化,也即模拟了一个字符流,需要通过insert函数每次插入一个字符,然后通过 fisrt_appear_once获取当前状态下的字符串里的第一个只出现一次的字符)

# 思路

(刷题后遗症就是这道题明明觉得有好多种方法可以实现,但还是怀疑自己没有想到最优的方法,也是醉了……)

最基本的方法就是,从当前串开头开始读,读到每个字符就往后扫一遍看是否有重样的, 直到找到第一个不重样的。时间复杂度n^2。

另外方法自然就是建hash表啦,先扫一遍串,记下每个字符出现的次数,再扫一遍串同时查询每个字符出现的次数,遇到出现次数为1的就是第一个只出现一次的字符了。

# 代码

# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.stream = []
        self.counter = {}
    # 返回对应char
    def FirstAppearingOnce(self):
        for c in self.stream:
            if self.counter[c] == 1:
                return c
        return '#'
        
    def Insert(self, char):
        if char in self.counter:
            self.counter[char] += 1
        else:
            self.counter[char] = 1
        self.stream.append(char)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 链表中环的入口结点

# 问题

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

# 思路

首先要确定是否包含环。(这个题在第二版书中是位于 链表倒数第k个结点 之后的,所以思路上会有些类似。)确定的方法是,两个指针同时从头出发,一个一次走一步,一个一次走两步,当快指针和慢指针重逢了,说明存在环,如果快指针走到了链表末尾,也没有和慢的重逢过,说明不包含环。

第二步是找出环的入口。仍然可以用两个指针,如图的环有4个节点,那么让p1先走4步,然后p1 p2 一起走,当两个指针重逢时,由于两个指针差距应该是环的长度,而他俩重逢了,说明p1已经绕环走了一圈,重逢的位置就是环的入口。

如何找到环的长度?在前面判断是否有环时,如果快慢指针相遇则说明有环,此时相遇的节点必定在环中,记住这个节点然后从它出发,等再次回到这个节点时就走了一圈,就得到环的长度了。

所以这个题的关键在于,要知道如何判断是否包含环、如何计算环长度、如何通过两个指针差距的方法找到环的入口。

# 代码

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def EntryNodeOfLoop(self, pHead):
        if not pHead:
            return None
        fast = pHead
        slow = pHead
        has_loop = False
        # 先判断是否有环
        while fast:
            if fast.next:
                fast = fast.next.next
            else:  # fast已经走到最后的节点了
                return None
            slow = slow.next
            if fast == slow:
                has_loop = True
                break
        if not has_loop:
            return None
        # 统计环中节点个数
        count = 1
        fast = fast.next
        while slow != fast:
            fast = fast.next
            count += 1
        # 寻找环的入口
        fast = pHead
        slow = pHead
        while count > 0:
            fast = fast.next
            count -= 1
        while fast != slow:
            fast = fast.next
            slow = slow.next
        return fast
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

另有一种很简洁的python写法:

class Solution:
    def EntryNodeOfLoop(self, pHead):
        slow, fast = pHead, pHead
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:  # 有环
                slow2 = pHead  # 直接让slow继续走,另从head同步开始走了??
                while slow != slow2:
                    slow = slow.next
                    slow2 = slow2.next
                return slow
1
2
3
4
5
6
7
8
9
10
11
12

为何判断有环之后,从相遇的点开始,直接让slow和head出发的slow2继续走,等相遇时就是入口了呢?难道不需要先计算一下环长?

不妨这样来算一下,当fast和slow相遇时, l_fast = l_slow + ring = 2 x l_slow,换言之,l_slow = ring ! 相遇时slow走过的距离,就已经是环的长度了!所以无需再去费劲计算环长,还要让一个指针先走过环长步,因为费了一大圈兜兜转转,最开始相遇的地方,就已经是要找的地方了……

# 删除链表中重复的结点

# 问题

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

# 思路

首先构造一个空的头节点(以防出现 1->1 这样的链表,应当返回空),然后有两个指针,pre指向当前已经处理好了的链表,而last指向当前正在处理的节点。

对于每个节点

  • 如果它和它后面的值相同,那么就让它一直往后走,直到它和它后面的值不相同,然后把它后面的节点,接到当前处理完链表的后面;
  • 如果它和它后面的值不同,就直接接到链表上,然后继续往后找即可

# 代码

class Solution:
    def deleteDuplication(self, pHead):
		# 空,或者只有一个节点,直接返回
        if (not pHead) or (not pHead.next):
            return pHead
		# 构造一个新的头节点
        head = ListNode(0)
        head.next = pHead
        pre = head
        last = head.next
        while last:
			# 当前节点有next,且和它的next值相同,说明这一串都要被抛弃
            if last.next and last.val == last.next.val:
                while last.next and last.val == last.next.val:
                    last = last.next
                pre.next = last.next
                last = last.next
			# 当前节点没有next,或者和它的next值不同,则将此节点接入链表,并继续往后走
            else:
                pre = pre.next
                last = last.next
        return head.next
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上次更新: 2022/11/11, 2:11:00
剑指Offer(12)
剑指Offer(14)

← 剑指Offer(12) 剑指Offer(14)→

Theme by Vdoing | Copyright © 2017-2023 anthony 京ICP备17072417-3
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式