前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >每日一刷《剑指offer》字符串篇之第一个只出现一次的字符

每日一刷《剑指offer》字符串篇之第一个只出现一次的字符

作者头像
终有救赎
修改2023-11-14 10:33:39
1890
修改2023-11-14 10:33:39
举报
文章被收录于专栏:多线程

第一个只出现一次的字符

难度:简单

描述

在一个长为 字符串中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)

举例

解题思路

方法一:哈希表;既然要找第一个只出现一次的字符,那只要我们统计每个字符在字符串中出现的次数,后续不就可以找到第一个只出现一次的字符了吗?

统计频率可以建立一个哈希表,遍历字符串的同时,统计每个字符出现的频率,然后再从头遍历一次字符串,在哈希表中查看每个字符串的频率,找到第一个只出现一次的字符串,返回位置,如果没找到返回-1即可。

方法二:队列+哈希表统计位置;上述方法一遍历了两次,有些繁琐,我们能不能在统计频率的过程中就找到第一个只出现一次的字符呢?利用先进先出的队列找到第一个位置!

首先我们还是利用了哈希表,但是这次我们不是统计频率,而是统计每个字符出现位置。遍历字符串,如果遇到哈希表中没有的字符,我们入哈希表,同将字符和位置同时各自入队,后续如果遇到了哈希表中出现的字符,那么这个字符势必不可能是我们要找的只出现一次的字符,在哈希表中将其位置置为-1:

代码语言:javascript
复制
//位置置为-1
mp[str[i]] = -1;

然后弹出队列中在前面的哈希表中位置为-1的字符。因为队列是先进先出,因此队列头记录的字符一定是第一次只出现一次的字符。

代码语言:javascript
复制
while(!q.empty() && mp[q.front().first] == -1) 
    q.pop();

空队列则代表没有找到。

实现代码(java)

方法一:

代码语言:javascript
复制
import java.util.*;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        HashMap<Character, Integer> mp = new HashMap<>();
        //统计每个字符出现的次数
        for(int i = 0; i < str.length(); i++) 
            mp.put(str.charAt(i), mp.getOrDefault(str.charAt(i), 0) + 1);
        //找到第一个只出现一次的字母
        for(int i = 0; i < str.length(); i++) 
            if(mp.get(str.charAt(i)) == 1)
                return i;
        //没有找到
        return -1; 
    }
}

方法二:

代码语言:javascript
复制
import java.util.*;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        //统计字符出现的位置
        HashMap<Character, Integer> mp = new HashMap<>();
        Queue<Character> q1 = new LinkedList<>();
        Queue<Integer> q2 = new LinkedList<>();
        for(int i = 0; i < str.length(); i++){
            //没有出现过的字符
            if(!mp.containsKey(str.charAt(i))){ 
                mp.put(str.charAt(i), i);
                q1.offer(str.charAt(i));
                q2.offer(i);
            //找到重复的字符
            }else{ 
                //位置置为-1
                mp.put(str.charAt(i), -1);
                //弹出前面所有的重复过的字符
                while(!q1.isEmpty() && mp.get(q1.peek()) == -1){
                    q1.poll();
                    q2.poll();
                }
            }
        }
        return q2.isEmpty() ? -1 : q2.poll();    
    }
}

学习完本题的思路你可以解决如下题目:

JZ12. 矩阵中的路径

矩阵中的路径

难度:中等

描述

请设计一个函数,用来判断在一个n乘m的矩阵中是否存在一条包含某长度为len的字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如

矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

举例

解题思路

方法一:递归(推荐使用);要在矩阵中找到从某个位置开始,位置不重复的一条路径,路径为某个字符串,那我们肯定是现在矩阵中找到这个位置的起点。没办法直观的找出来,只能遍历矩阵每个位置都当作起点试一试。

找到起点后,它周围的节点是否可以走出剩余字符串子串的路径,该子问题又可以作为一个递归。因此,可以用递归来解决。

  • 终止条件: 到了矩阵的边界或者是下一个字符与这个位置的字符不匹配,或者字符串匹配完成,都需要返回,
  • 返回值: 将每条查询路径是否有这样的字符串返回,即true or false。
  • 本级任务: 检查这个位置的字符与字符串中这个字符是否匹配,并向与它相邻的四个方向(且不是来的方向)延伸子问题。
代码语言:javascript
复制
dfs(matrix, n, m, i - 1, j, word, k + 1, flag)
||dfs(matrix, n, m, i + 1, j, word, k + 1, flag)
||dfs(matrix, n, m, i, j - 1, word, k + 1, flag)
||dfs(matrix, n, m, i , j + 1, word, k + 1, flag)

同时,在走的过程中,要设置一个和矩阵大小相同的bool矩阵表示是否已经访问,如果某个结点访问了,在同一条路线上就不能再访问了,其他路线依旧可以访问,所以若是某条路线不达标,需要回溯,将其改回来。

代码语言:javascript
复制
flag[i][j] = true;
...//递归
//没找到经过此格的,此格未被占用
flag[i][j] = false; 

实现代码(java)

代码语言:javascript
复制
import java.util.*;
public class Solution {
    private boolean dfs(char[][] matrix, int n, int m, int i, int j, String word, int k, boolean[][] flag){
        if(i < 0 || i >= n || j < 0 || j >= m || (matrix[i][j] != word.charAt(k)) || (flag[i][j] == true))
            //下标越界、字符不匹配、已经遍历过不能重复
            return false;
        //k为记录当前第几个字符
        if(k == word.length() - 1) 
            return true;
        flag[i][j] = true;
        //该结点任意方向可行就可
        if(dfs(matrix, n, m, i - 1, j, word, k + 1, flag)
          ||dfs(matrix, n, m, i + 1, j, word, k + 1, flag)
          ||dfs(matrix, n, m, i, j - 1, word, k + 1, flag)
          ||dfs(matrix, n, m, i , j + 1, word, k + 1, flag))
            return true; 
        //没找到经过此格的,此格未被占用
        flag[i][j] = false; 
        return false;
    }
    
    public boolean hasPath (char[][] matrix, String word) {
        //优先处理特殊情况
        if(matrix.length == 0)
            return false;
        int n = matrix.length;
        int m = matrix[0].length;
        //初始化flag矩阵记录是否走过
        boolean[][] flag = new boolean[n][m];
        //遍历矩阵找起点
        for(int i = 0; i < n; i++){  
            for(int j = 0; j < m; j++){
                //通过dfs找到路径
                if(dfs(matrix, n, m, i, j, word, 0, flag))
                    return true;
            }
        }
        return false;
    }
}

学习完本题的思路你可以解决如下题目:

JZ13. 机器人的运动范围

机器人的运动范围

难度:困难

描述

地上有一个 rows 行和 cols 列的方格。坐标从 0,0 到 rows-1,cols-1 。一个机器人从坐标 0,0 的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 threshold 的格子。 例如,当 threshold 为 18 时,机器人能够进入方格   35,37 ,因为 3+5+3+7 = 18。但是,它不能进入方格 35,38 ,因为 3+5+3+8 = 19 。请问该机器人能够达到多少个格子?

举例

解题思路

方法一:DFS(深度优先搜索)

这道题说的是一个机器人从左上角开始,他可以沿着上下左右四个方向走,并且走到的每个格子坐标的数字和不大于k,问可以走多少个格子。我们先来画个图看一下

这里统计的是能走多少个格子,所以统计肯定是不能有重复的,题中说了,机器人是可以沿着上下左右四个方向走的。但你想一下,任何一个格子你从任何一个方向进来(比如从上面进来),那么他只能往其他3个方向走,因为如果在往回走就重复了。但实际上我们只要沿着两个方向走就可以了,一个是右边,一个是下边,也就是上面图中红色的箭头。

实现代码(java)

代码语言:javascript
复制
    public int movingCount(int threshold, int rows, int cols) {
        //临时变量visited记录格子是否被访问过
        boolean[][] visited = new boolean[rows][cols];
        return dfs(0, 0, rows, cols, threshold, visited);
    }

    public int dfs(int i, int j, int rows, int cols, int threshold, boolean[][] visited) {
        //i >= rows || j >= cols是边界条件的判断,threshold < sum(i, j)判断当前格子坐标是否
        // 满足条件,visited[i][j]判断这个格子是否被访问过
        if (i >= rows || j >= cols || threshold < sum(i, j) || visited[i][j])
            return 0;
        //标注这个格子被访问过
        visited[i][j] = true;
        //沿着当前格子的右边和下边继续访问
        return 1 + dfs(i + 1, j, rows, cols, threshold, visited) +
                dfs(i, j + 1, rows, cols, threshold, visited);
    }

    //计算两个坐标数字的和
    private int sum(int i, int j) {
        int sum = 0;
        //计算坐标i所有数字的和
        while (i != 0) {
            sum += i % 10;
            i /= 10;
        }
        //计算坐标j所有数字的和
        while (j != 0) {
            sum += j % 10;
            j /= 10;
        }
        return sum;
    }

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-11-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第一个只出现一次的字符
    • 描述
      • 举例
        • 解题思路
          • 实现代码(java)
          • 矩阵中的路径
            • 描述
              • 举例
                • 解题思路
                  • 实现代码(java)
                  • 机器人的运动范围
                    • 描述
                      • 举例
                        • 解题思路
                          • 实现代码(java)
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档