Python小白 Leetcode刷题历程 No.36-No.40 有效的数独、解数独、外观数列、组合总和、组合总和Ⅱ (有题干 有代码 有思路心得)

Python小白 Leetcode刷题历程 No.36-No.40 有效的数独、解数独、外观数列、组合总和、组合总和Ⅱ

写在前面:

作为一个计算机院的大学生,总觉得仅仅在学校粗略的学习计算机专业课是不够的,尤其是假期大量的空档期,作为一个小白,实习也莫得路子,又不想白白耗费时间。于是选择了Leetcode这个平台来刷题库。编程我只学过基础的C语言,现在在自学Python,所以用Python3.8刷题库。现在我Python掌握的还不是很熟练,算法什么的也还没学,就先不考虑算法上的优化了,单纯以解题为目的,复杂程度什么的以后有时间再优化。计划顺序五个题写一篇日志,希望其他初学编程的人起到一些帮助,写算是对自己学习历程的一个见证了吧。

有一起刷LeetCode的可以关注我一下,我会一直发LeetCode题库Python3解法的,也可以一起探讨。

觉得有用的话可以点赞关注下哦,谢谢大家!
········································································································································································

No.36.有效的数独

难度:中等
题目描述:


Python小白 Leetcode刷题历程 No.36-No.40 有效的数独、解数独、外观数列、组合总和、组合总和Ⅱ (有题干 有代码 有思路心得)_第1张图片
在这里插入图片描述

Python小白 Leetcode刷题历程 No.36-No.40 有效的数独、解数独、外观数列、组合总和、组合总和Ⅱ (有题干 有代码 有思路心得)_第2张图片
在这里插入图片描述

题解代码(Python3.8)

class Solution:
    def isValidSudoku(self, board: List[List[str]]) -> bool:
        set_box=set()
        for i in range(9):
            for j in range(9):
                if board[i][j].isdigit():
                    row = 'row_'+str(i)+' ' + board[i][j]
                    col = 'col_'+str(j)+' ' + board[i][j]
                    small_square = 'row-col_'+str(i//3)+'-'+str(j//3)+' ' + board[i][j]
                    if  row in set_box or col in set_box or small_square in set_box:
                        return False
                    set_box.update([row,col,small_square])
        return True

或许有用的知识点:
set.add(key)可以在set中添加一个元素,set.remove(key)可以在set中删除一个元素。
如果想在set中一次性假如多个元素,可使用set.update([key1,key2,key3])。

解题思路:
在数独9宫格中,每个数字必定带有三个特性:行数、列数、小方块位置。对于一个i行j列的元素n,我们不妨定义它的特性为,row=row_i n , col=col_j n , small_square=row-col_(i//3)-(j)//3 n。对于每个数,我们将他的三个特性储存到一个set中。当新一个数字的三个特性都不在set中,我们便将其写入set;如果新一个数字有特性已经在set中了,不满足条件,返回False。

No.37.解数独

难度:困难
题目描述:


Python小白 Leetcode刷题历程 No.36-No.40 有效的数独、解数独、外观数列、组合总和、组合总和Ⅱ (有题干 有代码 有思路心得)_第3张图片
在这里插入图片描述

在这里插入图片描述

题解代码(Python3.8)

class Solution:
    def solveSudoku(self, board: List[List[str]]) -> None:
        row = [set(range(1,10)) for _ in range(9)]      #行剩余可用数字
        col = [set(range(1,10)) for _ in range(9)]      #列剩余可用数字
        block = [set(range(1,10)) for _ in range(9)]    #块剩余可用数字
        empty = []                                      #收集空白位置 
        for i in range(9):
            for j in range(9):
                if board[i][j] != '.':
                    val = int(board[i][j])
                    row[i].remove(val)
                    col[j].remove(val)
                    block[(i//3)*3+(j//3)].remove(val)
                else:
                    empty.append((i,j))
        
        def backtrack(iter_=0):
            if iter_ == len(empty):
                return True
            i,j = empty[iter_]
            block_index = (i//3)*3+(j//3)
            for val in row[i] & col [j] & block[block_index]:
                row[i].remove(val)
                col[j].remove(val)
                block[block_index].remove(val)
                board[i][j] = str(val)
                if backtrack(iter_+1):
                    return True
                row[i].add(val)
                col[j].add(val)
                block[block_index].add(val)
            return False

        backtrack()

或许有用的知识点:
这道题需要使用回溯算法。
set( range(j) ) for _ in range(i),可以制作一个i行j列的set型二维数组,例如set( range(3)) for _ in range(4) = [ {0,1,2} , {0,1,2} , {0,1,2} , {0,1,2} ]。
要求列表A,B,C的公共元素,应使用 A & B & C ,(逻辑运算 and or not 只会返回运算的布尔值)。

解题思路:
先确定回溯函数,对比回溯函数模板,回溯出口为iter_长度与empty相等,回溯主体中在当发现子状态的有效解后进入下一状态,否则回溯到原来状态。
之后初始化状态,要有行剩余可用数字、列剩余可用数字、块剩余可用数字、空白方格位置。最后调用回溯函数即可。

No.38.外观数列

难度:简单
题目描述:


Python小白 Leetcode刷题历程 No.36-No.40 有效的数独、解数独、外观数列、组合总和、组合总和Ⅱ (有题干 有代码 有思路心得)_第4张图片
在这里插入图片描述

题解代码(Python3.8)

class Solution:
    def countAndSay(self, n: int) -> str:
        prev_person = '1'
        for i in range(1,n):
            next_person,num,count = '',prev_person[0],1
            for j in range(1,len(prev_person)):
                if prev_person[j] == num:
                    count +=1
                else:
                    next_person += str(count)+num
                    num = prev_person[j]
                    count =1
            next_person += str(count)+num
            prev_person = next_person
        return prev_person

解题思路:
这个题就是题比较难读懂,我翻译了一下,其实就是:
1.1
2.描述的是1,是一个1,也就是11
3.描述的是11,是两个1,也就是21

  1. 描述的是21,是一个2一个1,也就是12-11
  2. 描述的是1211, 是一个1,一个2,两个1,也就是11-12-21
  3. 描述的是111221,是三个1,两个2,一个1,也就是31-22-11
  4. 描述的是312211,是一个3一个1两个2两个1,也即是13-11-22-21
    以此类推
    所以对字符串进行一次遍历就好,将已生成的最后一个数称为‘上一个数’,再通过‘上一个数’推出‘下一个数’,然后把‘下一个数’的值赋给上一个数,继续遍历即可。

No.39.组合总和

难度:中等
题目描述:


Python小白 Leetcode刷题历程 No.36-No.40 有效的数独、解数独、外观数列、组合总和、组合总和Ⅱ (有题干 有代码 有思路心得)_第5张图片
在这里插入图片描述

题解代码(Python3.8)

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        l=len(candidates)
        if l==0:
            return []
        candidates.sort()
        path=[]
        res=[]
        
        def backtrack(begin,path,target):
            if target==0:
                res.append(path[:])
            for i in range(begin,l):
                target_next=target-candidates[i]        #剪枝操作
                if target_next<0:
                    break
                path.append(candidates[i])
                backtrack(i,path,target_next)
                path.pop()
        
        backtrack(0,path,target)
        return res

或许有用的知识点:
这道题要用到回溯算法。
a=p与a=p[:]的区别:a=p是把p的地址给a,p和a指向同一对象;而a=p[:]是创建一个内容与p全等的对象,命名为a,a指向p的副本,p和a指向不同对象。再回溯算法中,对可能被回溯的待保存元素,有时需要以a=p[:] 的方式储存。

解题思路:
定义回溯函数,在减的过程中,得到 0或者负数,就没有必要再走下去,所以这两种情况就分别表示成为叶子结点。此时递归结束,然后要发生回溯。注意,这道题中组合里的数字可以无限制重复被选取。而要想完成剪枝,前提是数组是有序的,因此我们需要对数组进行排序。注意,Python 中可变对象是引用传递,因此需要将当前 path 里的值拷贝出来,即a=p[:]。

No.40.组合总和Ⅱ

难度:中等
题目描述:


Python小白 Leetcode刷题历程 No.36-No.40 有效的数独、解数独、外观数列、组合总和、组合总和Ⅱ (有题干 有代码 有思路心得)_第6张图片
在这里插入图片描述

题解代码(Python3.8)

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        l=len(candidates)
        if l==0:
            return []
        candidates.sort()                                           #初始化
        path=[]
        res=[]
        
        def backtrack(begin,path,target):
            if target==0:                                           #回溯出口
                res.append(path[:])
            for i in range(begin,l):                                #回溯主体
                target_next=target-candidates[i]    #剪枝操作
                if target_next<0:
                    break
                if i>begin and candidates[i-1]==candidates[i]:
                    continue
                path.append(candidates[i])
                backtrack(i+1,path,target_next)
                path.pop()                                          #状态返回
        
        backtrack(0,path,target)
        return res

或许有用的知识点:
这道题要用到回溯算法。
a=p与a=p[:]的区别:a=p是把p的地址给a,p和a指向同一对象;而a=p[:]是创建一个内容与p全等的对象,命名为a,a指向p的副本,p和a指向不同对象。再回溯算法中,对可能被回溯的待保存元素,有时需要以a=p[:] 的方式储存。

解题思路:
这道题跟‘39.组合总和’的思路类似,都是定义回溯函数,与其差别就是这道题组合中每个数只能用使用一次。在减的过程中,得到 0或者负数,就没有必要再走下去,所以这两种情况就分别表示成为叶子结点。此时递归结束,然后要发生回溯。要想完成剪枝,前提是数组是有序的,因此我们需要对数组进行排序。注意,Python 中可变对象是引用传递,因此需要将当前 path 里的值拷贝出来,即a=p[:]。

你可能感兴趣的:(Python小白 Leetcode刷题历程 No.36-No.40 有效的数独、解数独、外观数列、组合总和、组合总和Ⅱ (有题干 有代码 有思路心得))