图解数据结构刷题日志【第一周】(2022/10/20-2022/10/23)
1. 剑指offer 05. 替换空格
1.1 题目
```java
请实现一个函数,把字符串 s 中的每个空格替换成"%20"
示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."
限制:
0 <= s 的长度 <= 10000
```
1.2 解题思路
逐个字符遍历String字符串,并设置一个结果字符串变量re。
如果遍历到的字符不是空格,就把字符存入re;如果遍历到的字符是空格,则将“%20”存入re;
由于结果变量re需要增加字符,而String类型有一个特点:String类是不可改变的,所以你一旦创建了String对象,那它的值就无法改变了,因此re不能定义为string类型,re应该选择使用 StringBuffer 或 StringBuilder 类。
1.3 数据类型功能函数总结
//String
String.length();//获得字符串长度
String.charAt(index);//获得字符串中下标index的字符
//StringBuffer
StringBuffer 变量名=new StringBuffer();//声明一个StringBuffer类型的变量
StringBuffer.append(ch);//在尾部添加字符
StringBuffer.toString();//将StringBuffer转为String类型
1.4 java代码
class Solution {
public String replaceSpace(String s) {
StringBuilder re=new StringBuilder();
for(int i=0;i<s.length();i++){
if(s.charAt(i)==' '){
re.append('%');
re.append('2');
re.append('0');
}
else{
re.append(s.charAt(i));
}
}
return re.toString();
}
}
2. 剑指offer 06. 替换空格
2.1 题目
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
限制:
0 <= 链表长度 <= 10000
2.2 解题思路
定义一个数组来作为返回结果,首先要获得数组的确定长度,使用一次遍历来获得,然后定义数组,之后需要从头到尾遍历链表,从尾到头填充数组。
思路比较简单,适合小白入门。
2.3 数据类型功能函数总结
//数组定义
int[] arrayName=new int[arrayLen];
//链表的属性访问
LinkNode.属性名;
2.4 java代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
int length=0;
ListNode h=head;
while(h!=null){
h=h.next;
length++;
}
int[] a=new int[length];
h=head;
length-=1;
while(h!=null){
a[length]=h.val;
length--;
h=h.next;
}
return a;
}
}
3. 剑指offer 09. 用两个栈实现队列
3.1 题目
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead?操作返回 -1 )
示例 1:
输入:
["CQueue","appendTail","deleteHead","deleteHead","deleteHead"]
[[],[3],[],[],[]]
输出:
[null,null,3,-1,-1]
示例 2:
输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:
[null,-1,null,null,5,2]
提示:
1 <= values <= 10000
最多会对?appendTail、deleteHead 进行?10000?次调用
3.2 解题思路
用两个栈实现队列,而队列需要做到的是尾部插入头部取出,栈则是头部插入头部取出。
CQueue()的实现:两个栈进行初始化
appendTail()和deleteHead()需要借助两个栈来实现,解题思路里面将栈a作为添加尾部元素的主要栈,栈b作为辅助栈,在取出头部元素时使用。
appendTail()只需要将栈a的顶部看作是队列的尾部,直接插入即可。
deleteHead()比较复杂:
- 栈a中的底部元素是我们想要取出的头部元素,因此我们需要使用栈b这个辅助栈实现栈a的倒序,我们想要得到的元素就是此时栈b的顶部元素。
- 之后,由于我们需要将栈a作为操作的主要栈,因此我们将栈b的元素再退还给栈a,恢复栈a为主栈,栈b为空的情况。
- 如果队列为空,则主栈a是空的,因此我们可以通过判断a是不是空来考虑是不是返回-1
3.3 数据类型功能函数总结
//栈的相关操作
Stack<> stackName=new Stack<>();//定义一个栈
Stack.push(element);//元素压入栈中
Stack.pop();//顶部元素弹出栈
Stack.empty();//判断栈是不是空,如果是空返回true,不是空返回false
//LinkedList的相关操作
LinkedList<> LLName=new LinkedList<>();//定义一个链表
LinkedList.removeLast();//从链表的末尾删除元素
LinkedList.addLast(element);//从链表的末尾添加元素
LinkedList.removeFirst();//从链表的头部删除元素
LinkedList.addFirst(element);//从链表的头部添加元素
LinkedList.isEmpty();//判断链表是不是空,是空返回true,不是空返回false
3.4 java代码
class CQueue {
Stack<Integer> a;//添加尾部元素
Stack<Integer> b;//取出头部【栈底】元素
//两个栈实现队列:
//定义队列:定义两个栈
//添加元素:
public CQueue() {//定义队列
a = new Stack<Integer>();//添加尾部元素
b = new Stack<Integer>();//取出头部【栈底】元素
}
public void appendTail(int value) {//在尾部添加元素
a.push(value);
}
public int deleteHead() {//取出头部元素
if(a.empty()==true){
return -1;
}
else{//说明可以取出
while(a.empty()!=true){
int element=a.pop();
b.push(element);
}
int re=b.pop();
while(b.empty()!=true){
int element=b.pop();
a.push(element);
}
return re;
}
}
}
/**
* Your CQueue object will be instantiated and called as such:
* CQueue obj = new CQueue();
* obj.appendTail(value);
* int param_2 = obj.deleteHead();
*/
3.5 进阶解法
由于对算法的时空花销不太满意,所以偷偷看一眼官方题解,理解消化一下
官方题解给出的思路是“首尾分离”,
之前的思路之所以执行用时巨大,主要是因为从主栈a将数据转入辅栈b,又从辅栈b将数据转入主栈a,这样的两次转移,在数据量较大的情况下无疑会花费很多时间。
因此,改进思路是省去了从辅栈b再次将数据转入主栈a的过程,这样,a和b中都可能有数据。
对于添加元素appendTail()来说,还是将数据放在栈a的顶部。
而对于deleteHead()来说,有多种不同的情况
1.如果a和b都是空,说明队列中没有元素,返回-1
2.如果a和b有一个是空
(1)a是空,而b不是空,那么直接从b的顶部取出元素,不必考虑a是不是空
(2)b是空,a不是空,那么要取的是a的栈底元素,需要将a的元素转入b中,然后再取出b的栈顶元素
3.如果a和b都不是空
直接从b的顶部取出元素
因此,b不为空的时候,说明队列中必定有元素,直接将b的顶部元素取出即可。
b为空的时候,则要根据a是不是空,来考虑队列中有没有元素。
java代码如下:
class CQueue {
Stack<Integer> a;//添加尾部元素
Stack<Integer> b;//取出头部【栈底】元素
//两个栈实现队列:
//定义队列:定义两个栈?
//添加元素:
public CQueue() {//定义队列
a = new Stack<Integer>();//添加尾部元素
b = new Stack<Integer>();//取出头部【栈底】元素
}
public void appendTail(int value) {//在尾部添加元素
a.push(value);
}
public int deleteHead() {//取出头部元素
if(b.empty()&&a.empty()){return -1;}
else{//说明可以取出
if(b.empty()){//b是空,a不是空,需要将a中的元素都转移到b中
while(a.empty()!=true){
b.push(a.pop());
}
return b.pop();
}
else{//a不管是不是空,b不是空,对b中取出元素没有影响
return b.pop();
}
}
}
}
/**
* Your CQueue object will be instantiated and called as such:
* CQueue obj = new CQueue();
* obj.appendTail(value);
* int param_2 = obj.deleteHead();
*/
可以看到运行的时间明显降低了,因为少了一次栈转移的循环操作,节约时间。
官方题解里面还用到了LinkedList
这个数据结构,也就是链表结构,对性能有一定程度的提升。但是本题要求用栈结构来实现队列,因此我觉得用链表操作有点奇怪。
4. 剑指offer 20. 表示数值的字符串
4.1 题目
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
数值(按顺序)可以分成以下几个部分:
若干空格
一个 小数 或者 整数
(可选)一个 'e' 或 'E' ,后面跟着一个 整数
若干空格
小数(按顺序)可以分成以下几个部分:
(可选)一个符号字符('+' 或 '-')
下述格式之一:
至少一位数字,后面跟着一个点 '.'
至少一位数字,后面跟着一个点 '.' ,后面再跟着至少一位数字
一个点 '.' ,后面跟着至少一位数字
整数(按顺序)可以分成以下几个部分:
(可选)一个符号字符('+' 或 '-')
至少一位数字
部分数值列举如下:
["+100", "5e2", "-123", "3.1416", "-1E-16", "0123"]
部分非数值列举如下:
["12e", "1a3.14", "1.2.3", "+-5", "12e+5.4"]
示例 1:
输入:s = "0"
输出:true
示例 2:
输入:s = "e"
输出:false
示例 3:
输入:s = "."
输出:false
示例 4:
输入:s = " .1 "
输出:true
提示:
1 <= s.length <= 20
s 仅含英文字母(大写和小写),数字(0-9),加号 '+' ,减号 '-' ,空格 ' ' 或者点 '.' 。
4.2 解题思路
这道题是编译原理里面很经典的“判断数字”问题。
虽然本科学过这块的知识,但是真的转化成代码还是困难重重,最后还是很遗憾没有做出来,看的官方题解。
首先,根据判断过程定义若干个状态,状态之间的切换是根据当前读到的字符决定下一个状态。
状态机的设计:
初始状态:S0,在“数字的构成”中,状态初始值为S0,并且前面读了若干个空格依然可以处于S0
S0状态如何转换为下一个状态:通过读+/-号或者数字或者".",而读其他情况则表示字符串不是数字。
S1表示读到+/-号,通过读数字或者’.‘转换为下一个状态,读到其他均表示字符串不是数字。
S2表示读到数字,下一个字符为数字维持S2状态,读到’.'转移为下一个状态。
……
以此类推,最终的状态转移图可见官方图解。
使用map存储状态转移表,循环遍历字符串,根据当前的状态实现状态转移。
当字符串结束时,状态机处于结束状态,则表示是数字,否则不是数字。
4.3 数据类型功能函数总结
//HashMap相关操作
HashMap<> HM_Name=new HashMap<>();//定义
HashMap<> HM_Name=new HashMap<>() {{put(key,value);put(key,value);}};//定义并初始化
HashMap.get(key);//查找key对应的value并返回
HashMap.containsKey(key);//在map表中是不是存在key,存在的话返回true,不存在返回false
//字符串相关操作
string.toCharArray(str);//将字符串转化为字符型数组,用于遍历。
for(char c:string.toCharArray()){}//字符串的一种遍历方法
for(int i=0;i<string.length();i++){
string.charAt(i);
……
}//字符串的另一种遍历方式
4.4 java代码
class Solution {
public boolean isNumber(String s) {
Map[] states ={
new HashMap<>() {{put(' ',0);put('s',1);put('d',2);put('.',4);}},
new HashMap<>() {{put('d',2);put('.',4);}},
new HashMap<>() {{put('d',2);put('.',3);put('e',5);put(' ',8);}},
new HashMap<>() {{put('d',3);put('e',5);put(' ',8);}},
new HashMap<>() {{put('d',3);}},
new HashMap<>() {{put('s',6);put('d',7);}},
new HashMap<>() {{put('d',7);}},
new HashMap<>() {{put('d',7);put(' ',8);}},
new HashMap<>() {{put(' ',8);}}
};
int p=0;
char change;
for(char c:s.toCharArray()){
if(c>='0'&&c<='9'){change='d';}//输入数字
else if(c=='+'||c=='-'){change='s';}//输入±号
else if(c=='e'||c=='E'){change='e';}//输入幂符号
else if(c=='.'||c==' '){change=c;}
else{change='?';}
if(states[p].containsKey(change)!=true){//没有这个有效转移字符
return false;
}
p=(int) states[p].get(change);
}
return p==2||p==3||p==7||p==8;
}
}