图解数据结构刷题日志【第一周】(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;
     }
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值