Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 题目链接:https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/?envType=study-plan&id=lcof
// day10/31
// 第 10 天主题为:动态规划(中等)
// 包含两道题目:
// 剑指offer46.把数字翻译成字符串
// 剑指offer48.最长不含重复字符的子字符串
package main

import "strconv"

//比较明显的动态规划题目,设 dp[i]代表s[:i+1]的翻译方法数目,dp[i] 明显依赖于 dp[i-1] 与 dp[i-2],类似于斐波那契数是吧,
//本题有点不一样的地方在于 dp[i]=dp[i-1]+dp[i-2] 是有条件的
//- 当 s[i-1] 与 s[i] 组成的数字小于 25 时,s[i] 可以与 s[i-1] 组合翻译,也可以分开翻译,dp[i]=dp[i-1]+dp[i-2],
//- 否则 s[i] 与 s[i-1] 无法组合翻译,只能单独翻译,dp[i] = dp[i-1]

//动态规划三步骤:
//- 确定dp数组大小及下标含义:dp[i] 代表 s[:i+1] 的翻译方法数目,len(dp)=len(string(num))
//- dp 数组初始化:dp[0]对应s[0],单个字符只有一种翻译方法,dp[0]=1,当 s[:2] 小于26 且 s[i]!=0 时,dp[1]=2,否则 dp[1]=1
//- 状态转移方程:从下标 2 开始遍历,x = strconv.Atoi(s[i-1:i+1]),并且判断 s[i-1] 是否为 0
//- 若 x < 26 且 s[i-1]!= 0,dp[i]=dp[i-1]+dp[i-2],s[i] 可以与 s[i-1] 组合翻译,也可以单独翻译
//- 否则,dp[i] = dp[i-1],s[i] 只能单独翻译,s[:i+1] 的翻译方法数目依赖于 s[:i]
// 最后,返回 dp[n-1] 即可。

//有一点需要注意,在状态转移方程那里一定要判断 s[i-1] 是否为 0,因为 “01”只能翻译为 “ab”,不能翻译成 “a",
//我第一次做这道题的时候就是忘记判断 s[i-1]是否为0,导致没有ac,刚才做的时候,又在这个地方跌到坑里了,有了再一再二,不会有再三再四了。
func translateNum(num int) int {
s := strconv.Itoa(num)
n := len(string(s))
if n == 0{
return 0
}
dp := make([]int,n)
dp[0] = 1
x,_ := strconv.Atoi(s[:2])
zero,_ := strconv.Atoi(string(s[0]))
if x < 26 && zero != 0{
dp[1] = 2
} else {
dp[1] = 1
}
for i:=2;i<n;i++{
x,_ = strconv.Atoi(s[i-1:i+1])
zero,_ := strconv.Atoi(string(s[i-1]))
if x < 26 && zero!= 0{
dp[i] = dp[i-1] + dp[i-2]
} else {
dp[i] = dp[i-1]
}
}
return dp[n-1]
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//题目链接:https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/?envType=study-plan&id=lcof

package main

//我们可以用一个字典存储每个字符最近一次出现的下标
//一次遍历字符串,变量 start 代表当前不含重复字符的子字符串的起始下标-1,初始化为 -1,
//为什么是 -1呢,因为我们遍历第一个字符的时候,其下标为 0,为不包含重复字符的子字符串,长度为0-(-1),返回变量res初始化为 0
//
//一次遍历字符串,for i,x := range s ,维护 s[start+1;i+1] 为不含重复字符的子串
//若当前遍历到的字符的最近一次出现下标大于 start(不会出现等于的情况),说明当前加入当前字符后,该字符串将包含重复字符,
//我们的做法是,将 start 更新到其最近一次出现的下标,这样就保证了加入当前字符后,我们目前的子字符串仍然不包含重复字符,
//
//否则(小于 start 或者 该字符在字符串中是第一次出现),当前不含重复字符的子串长度增加,更新 res 变量,

//最后,更新当前字符的最近出现下标。
func lengthOfLongestSubstring(s string) int {
start := -1
record := map[rune]int{}
res := 0
for i,x := range s{
if _,ok := record[x];ok && record[x] > start{
start = record[x]
} else {
res = max(res,i-start)
}
record[x] = i
}
return res
}

func max(x,y int) int {
if x > y{
return x
}
return y
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// 题目链接:https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof/
// 思路:建立辅助栈,与存储数据的栈大小相同,在向栈中存数据时,辅助栈同时存入一个数字
// 该辅助栈入栈元素的序列是一个非严格递增序列
// 如果该数据小于辅助栈栈顶元素,则辅助栈存入该数据,否则辅助栈还存入一个辅助栈的栈顶元素。
//(如果是存入第一个元素,辅助栈直接入栈即可,无比较操作)

package main

type MinStack struct {
nums []int //储存栈
min []int //辅助储存栈,存储最小值
}

/** initialize your data structure here. */
// 为解决命名冲突,这里函数名后+“2”,在LeetCode需删除
func Constructor2() MinStack {
return MinStack{
[]int{},
[]int{},
}
}

// 入栈时,存储栈直接入栈
// 对辅助栈,若栈长度为0,直接入栈
// 否则,与栈顶元素进行比较,若大于栈顶元素,入栈,否则,辅助栈再次存入栈顶元素
func (this *MinStack) Push(x int) {
this.nums=append(this.nums,x)
if len(this.min)==0{
this.min=append(this.min,x)
}else if this.min[len(this.min)-1]<x{
this.min=append(this.min,this.min[len(this.min)-1])
}else{
this.min=append(this.min,x)
}
}

// 出栈时,两栈均常规出栈即可
func (this *MinStack) Pop() {
this.nums=this.nums[:len(this.nums)-1]
this.min=this.min[:len(this.min)-1]
}

// 返回存储栈栈顶元素
func (this *MinStack) Top() int {
return this.nums[len(this.nums)-1]
}

// 求min值时,返回辅助栈栈顶元素即可
func (this *MinStack) Min() int {
return this.min[len(this.min)-1]
}

/**
* Your MinStack object will be instantiated and called as such:
* obj := Constructor();
* obj.Push(x);
* obj.Pop();
* param_3 := obj.Top();
* param_4 := obj.Min();
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 题目链接:https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/?envType=study-plan&id=lcof
// day1/31
// 第一天主题为:栈与队列(简单)
// 包含两道题目:
// 剑指offer09:用两个栈实现队列
// 剑指offer30.包含min函数的栈

//这题太经典了,两个栈实现队列。思路:两个栈,一个用来入栈(支持插入操作),一个用来出栈(支持删除操作),
//栈的特点是先进后出,队列是先进先出,不考虑中间的出入,那一批数据通过栈和队列后的顺序的刚好相反的。我
//们需要将栈的先进后出再倒一次顺序,就和队列的顺序相同了。
//
//官方描述:根据栈先进后出的特性,我们每次往第一个栈里插入元素后,栈顶是最后插入的元素,栈底是下一个待删除的元素。
//为了维护队列先进先出的特性,我们引入第二个栈,用第二个栈维护待删除的元素,在执行删除操作的时候我们首先看下第二个栈是否为空。
//如果为空,我们将第一个栈里的元素一个个弹出插入到第二个栈里,
//这样第二个栈里元素的顺序就是待删除的元素的顺序,要执行删除操作的时候我们直接弹出第二个栈的元素返回即可。

//具体实现:维护两个栈stack1和stack2,其中,stack1用来入队,stack2用于出队,初始时,两个栈均为空,插入元素时,stack1 插入元素即可
//删除元素时,若stack2为空,将stack1所有元素出栈至stack2,若stack2仍然为空,返回-1,否则,从stack2出栈一个元素并返回。
package main

// stack1与stack2分别用于入队和出队
type CQueue struct {
stack1 []int
stack2 []int
}

// 构造函数,初始时,两个栈均为空
func Constructor() CQueue {
return CQueue{[]int{},[]int{}}
}

// stack1用于入队
func (this *CQueue) AppendTail(value int) {
this.stack1 = append(this.stack1,value)
}

// stack2用于出队
func (this *CQueue) DeleteHead() int {
// 若stack2长度为0,将stack1所有元素出栈至stack2
if len(this.stack2) == 0{
for len(this.stack1) > 0{
x := this.stack1[len(this.stack1)-1]
this.stack2 = append(this.stack2,x)
this.stack1 = this.stack1[:len(this.stack1)-1]
}
}
// stack2出栈的元素即为队首元素
if len(this.stack2) > 0{
res := this.stack2[len(this.stack2)-1]
this.stack2 = this.stack2[:len(this.stack2)-1]
return res
}
// 若stack2长度仍为0,说明队列为空,返回-1
return -1
}


/**
* Your CQueue object will be instantiated and called as such:
* obj := Constructor();
* obj.AppendTail(value);
* param_2 := obj.DeleteHead();
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 题目解析在 用两个栈实现队列.go
// 本文件为 用链表实现的栈 来 实现队列
// 为解决命名冲突,本文件结构体与函数名后+1
package main

import "container/list"

type CQueue1 struct {
stack1, stack2 *list.List
}

func Constructor1() CQueue1 {
return CQueue1{
stack1: list.New(),
stack2: list.New(),
}
}

func (this *CQueue1) AppendTail1(value int) {
this.stack1.PushBack(value)
}

func (this *CQueue1) DeleteHead1() int {
if this.stack2.Len() == 0 {
for this.stack1.Len() > 0 {
this.stack2.PushBack(this.stack1.Remove(this.stack1.Back()))
}
}
if this.stack2.Len() != 0 {
e := this.stack2.Back()
this.stack2.Remove(e)
return e.Value.(int)
}
return -1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 题目链接:https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/?plan=lcof&plan_progress=klczit3
// day2/31
// 第二天主题为:链表(简单)
// 包含三道题目:
// 剑指offer06.从尾到头打印链表
// 剑指offer30.反转链表
// 剑指offer35.复杂链表的复制

// 解题思路:从尾到头打印链表,最后返回整数切片,也就是说,链表头节点的值为切片最后的元素
// 而链表尾结点的值为切片第一个元素
// 与链表的遍历结合,很容易想到先进后出的解题策略
// 说到”先进后出“,那必然会用到栈,先用栈来解题
// 从头到尾遍历链表,将节点依次入栈
// 遍历结束后,在从栈顶逐个输出节点的值至整数切片即可

package main


//Definition for singly-linked list.
type ListNode struct {
Val int
Next *ListNode
}


func reversePrint(head *ListNode) []int {
stack := []*ListNode{}
res := []int{}
if head == nil{
return []int{}
}
for head != nil{
stack = append(stack,head)
head = head.Next
}
for len(stack) > 0{
res = append(res,stack[len(stack)-1].Val)
stack = stack[:len(stack)-1]
}
return res
}

// 法2:递归在本质上是一个栈结构,针对本题,我们也可以使用递归来实现
// 容易想到,要实现反过来输出链表,每当我们访问到一个节点时,要将
// 当前节点的值放在返回切片的末尾,先递归输出其之后的节点,
// 当访问到当前的节点为空节点时,返回空切片即可
// 利用系统栈,实现了从尾到头打印链表
// 为避免命名冲突,此函数名后添加了后缀 ”_recursion“
func reversePrint_recursion(head *ListNode) []int {
if head == nil{
return []int{}
}
return append(reversePrint(head.Next),head.Val)
}
59 changes: 59 additions & 0 deletions LeetCode/剑指offer/day2_链表(简单)/反转链表.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 题目链接:https://leetcode.cn/problems/fan-zhuan-lian-biao-lcof/

// 解题思路:本题最通俗的解法为迭代,从头到尾遍历链表,不断更改当前节点的Next域
// 我们需要事先引入一个空节点,第一次迭代时,头结点指向pre,之后不断更新
// 更改Next域前,要记录当前节点的Next域指向的节点,防止链表出现断裂
// 做链表相关题目时,一定要谨防链表断裂的情况出现
// 此题的另一个要注意的点是代码的鲁棒性
// 表头指针为 null 或者整个链表只有一个节点时,我们要保证程序依旧能正常运行。

package main

/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/

// 关于Go语言中关于空节点的初始化需要格外注意,本题中对应pre的初始化
// 我最初的写法是 pre:=*ListNode{},报错: indirect of leetcode.ListNode{}
// ListNode{}的间接无效,不确定该如何翻译
// 若 pre 初始化为 nil,报错:use of untyped nil (solution.go)
// pre:=new(ListNode) 也是不可以的,最后输出的切片结果会多一个0元素
// 正确写法为 var pre *ListNode,此时pre为空节点,无零值,值为nil
// 好吧,这块是我对GO理解存在问题,为什么 *ListNode{}不可以,我需要再思考一下
func reverseList(head *ListNode) *ListNode {
var pre *ListNode
cur := head
for cur != nil{
pnext := cur.Next
cur.Next = pre
pre,cur = cur,pnext
}
// 遍历结束后,cur指向nil节点,pre指向原先链表的尾结点
return pre
}

// 法2:递归
// 理解:如果链表长度为2,结构为:a->b->nil 想要反转链表,可以用
// a.Next.Next=a
// a.Next=nil
// return b
// 这三行代码实现,明白这个,那递归就好理解了
// 假设链表长度大于2,当前正在处理b节点,b往后的节点已经完成反转
// 我们希望b指向a,则 a.Next.Next=a
// 若当目前处理节点为空,或其Next域为空时,返回该节点,即新链表的头结点
// 为避免命名冲突,本函数名后添加了后缀 ”_recursion“
func reverseList_recrusion(head *ListNode) *ListNode {
if head == nil || head.Next == nil{
return head
}
newhead := reverseList(head.Next)
head.Next.Next = head
// 虽然这行代码实质上只为原链表的头结点服务,但是仍然不可缺少
// 若无下面这行代码,链表有可能会形成环
head.Next = nil
return newhead
}
Loading