【算法挑战】字符的最短距离(含解析、源码)

news2025/2/23 1:53:56

821.字符的最短距离

https://leetcode-cn.com/problems/shortest-distance-to-a-character/

  • 821.字符的最短距离
    • 题目描述
    • 解法 1:中心扩展法
      • 思路
      • 复杂度分析
      • 代码 (JS/C++)
    • 解法 2:空间换时间
      • 思路
      • 复杂度分析
      • 代码 (JS/C++)
    • 解法 3:贪心
      • 思路
      • 复杂度分析
      • 代码 (JS/C++/Python)
    • 解法 4:窗口
      • 思路
      • 复杂度分析
      • 代码 (JS/C++/Python)

题目描述

给定一个字符串 S 和一个字符 C。返回一个代表字符串 S 中每个字符到字符串 S 中的字符 C 的最短距离的数组。

示例 1:

输入: S = "loveleetcode", C = 'e'
输出: [3, 2, 1, 0, 1, 0, 0, 1, 2, 2, 1, 0]
说明:

字符串 S 的长度范围为 [1, 10000]。
C 是一个单字符,且保证是字符串 S 里的字符。
S 和 C 中的所有字母均为小写字母。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shortest-distance-to-a-character
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法 1:中心扩展法

思路

这是最符合直觉的思路,对每个字符分别进行如下处理:

  • 从当前下标出发,分别向左、右两个方向去寻找目标字符 C
  • 只在一个方向找到的话,直接计算字符距离。
  • 两个方向都找到的话,取两个距离的最小值。

在这里插入图片描述

复杂度分析

  • 时间复杂度: O ( N 2 ) O(N^2) O(N2),N 为 S 的长度,两层循环。
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码 (JS/C++)

JavaScript Code

/**
 * @param {string} S
 * @param {character} C
 * @return {number[]}
 */
var shortestToChar = function (S, C) {
  // 结果数组 res
  var res = Array(S.length).fill(0);

  for (let i = 0; i < S.length; i++) {
    // 如果当前是目标字符,就什么都不用做
    if (S[i] === C) continue;

    // 定义两个指针 l, r 分别向左、右两个方向寻找目标字符 C,取最短距离
    let l = i,
      r = i,
      shortest = Infinity;

    while (l >= 0) {
      if (S[l] === C) {
        shortest = Math.min(shortest, i - l);
        break;
      }
      l--;
    }

    while (r < S.length) {
      if (S[r] === C) {
        shortest = Math.min(shortest, r - i);
        break;
      }
      r++;
    }

    res[i] = shortest;
  }
  return res;
};

C++ Code

class Solution {
public:
    vector<int> shortestToChar(string S, char C) {
        vector<int> res(S.length());

        for (int i = 0; i < S.length(); i++) {
            if (S[i] == C) continue;

            int left = i;
            int right = i;
            int dist = 0;

            while (left >= 0 || right <= S.length() - 1) {
                if (S[left] == C) {
                    dist = i - left;
                    break;
                }
                if (S[right] == C) {
                    dist = right - i;
                    break;
                }

                if (left > 0) left--;
                if (right < S.length() - 1) right++;
            }

            res[i] = dist;
        }

        return res;
    }
};

解法 2:空间换时间

思路

空间换时间是编程中很常见的一种 trade-off (反过来,时间换空间也是)。

因为目标字符 CS 中的位置是不变的,所以我们可以提前将 C 的所有下标记录在一个数组 cIndices 中。

然后遍历字符串 S 中的每个字符,到 cIndices 中找到距离当前位置最近的下标,计算距离。

复杂度分析

  • 时间复杂度: O ( N ∗ K ) O(N*K) O(NK),N 是 S 的长度,K 是字符 C 在字符串中出现的次数, K < = N K <= N K<=N
  • 空间复杂度: O ( K ) O(K) O(K),K 为字符 C 出现的次数,这是记录字符 C 出现下标的辅助数组消耗的空间。

代码 (JS/C++)

JavaScript Code

/**
 * @param {string} S
 * @param {character} C
 * @return {number[]}
 */
var shortestToChar = function (S, C) {
  // 记录 C 字符在 S 字符串中出现的所有下标
  var cIndices = [];
  for (let i = 0; i < S.length; i++) {
    if (S[i] === C) cIndices.push(i);
  }

  // 结果数组 res
  var res = Array(S.length).fill(Infinity);

  for (let i = 0; i < S.length; i++) {
    // 目标字符,距离是 0
    if (S[i] === C) {
      res[i] = 0;
      continue;
    }

    // 非目标字符,到下标数组中找最近的下标
    for (const cIndex of cIndices) {
      const dist = Math.abs(cIndex - i);

      // 小小剪枝一下
      // 注:因为 cIndices 中的下标是递增的,后面的 dist 也会越来越大,可以排除
      if (dist >= res[i]) break;

      res[i] = dist;
    }
  }
  return res;
};

C++ Code

class Solution {
public:
    vector<int> shortestToChar(string S, char C) {
        int n = S.length();
        vector<int> c_indices;
        // Initialize a vector of size n with default value n.
        vector<int> res(n, n);

        for (int i = 0; i < n; i++) {
            if (S[i] == C) c_indices.push_back(i);
        }

        for (int i = 0; i < n; i++) {
            if (S[i] == C) {
                res[i] = 0;
                continue;
            }

            for (int j = 0; j < c_indices.size(); j++) {
                int dist = abs(c_indices[j] - i);
                if (dist > res[i]) break;
                res[i] = dist;
            }
        }

        return res;
    }
};

解法 3:贪心

思路

其实对于每个字符来说,它只关心离它最近的那个 C 字符,其他的它都不管。所以这里还可以用贪心的思路:

  1. 从左往右 遍历字符串 S,用一个数组 left 记录每个字符 左侧 出现的最后一个 C 字符的下标;
  2. 从右往左 遍历字符串 S,用一个数组 right 记录每个字符 右侧 出现的最后一个 C 字符的下标;
  3. 然后同时遍历这两个数组,计算距离最小值。

优化 1

再多想一步,其实第二个数组并不需要。因为对于左右两侧的 C 字符,我们也只关心其中距离更近的那一个,所以第二次遍历的时候可以看情况覆盖掉第一个数组的值:

  1. 字符左侧没有出现过 C 字符
  2. i - left > right - i (i 为当前字符下标,left 为字符左侧最近的 C 下标,right 为字符右侧最近的 C 下标)

如果出现以上两种情况,就可以进行覆盖,最后再遍历一次数组计算距离。

优化 2

如果我们是直接记录 C 与当前字符的距离,而不是记录 C 的下标,还可以省掉最后一次遍历计算距离的过程。

复杂度分析

  • 时间复杂度: O ( N ) O(N) O(N),N 是 S 的长度。
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码 (JS/C++/Python)

JavaScript Code

/**
 * @param {string} S
 * @param {character} C
 * @return {number[]}
 */
var shortestToChar = function (S, C) {
  var res = Array(S.length);

  // 第一次遍历:从左往右
  // 找到出现在左侧的 C 字符的最后下标
  for (let i = 0; i < S.length; i++) {
    if (S[i] === C) res[i] = i;
    // 如果左侧没有出现 C 字符的话,用 Infinity 进行标记
    else res[i] = res[i - 1] === void 0 ? Infinity : res[i - 1];
  }

  // 第二次遍历:从右往左
  // 找出现在右侧的 C 字符的最后下标
  // 如果左侧没有出现过 C 字符,或者右侧出现的 C 字符距离更近,就更新 res[i]
  for (let i = S.length - 1; i >= 0; i--) {
    if (res[i] === Infinity || res[i + 1] - i < i - res[i]) res[i] = res[i + 1];
  }

  // 计算距离
  for (let i = 0; i < res.length; i++) {
    res[i] = Math.abs(res[i] - i);
  }
  return res;
};

直接计算距离:

JavaScript Code

/**
 * @param {string} S
 * @param {character} C
 * @return {number[]}
 */
var shortestToChar = function (S, C) {
  var res = Array(S.length);

  for (let i = 0; i < S.length; i++) {
    if (S[i] === C) res[i] = 0;
    // 记录距离:res[i - 1] + 1
    else res[i] = res[i - 1] === void 0 ? Infinity : res[i - 1] + 1;
  }

  for (let i = S.length - 1; i >= 0; i--) {
    // 更新距离:res[i + 1] + 1
    if (res[i] === Infinity || res[i + 1] + 1 < res[i]) res[i] = res[i + 1] + 1;
  }

  return res;
};

C++ Code

class Solution {
public:
    vector<int> shortestToChar(string S, char C) {
        int n = S.length();
        vector<int> dist(n, n);

        for (int i = 0; i < n; i++) {
            if (S[i] == C) dist[i] = 0;
            else if (i > 0) dist[i] = dist[i - 1] + 1;
        }

        for (int i = n - 1; i >= 0; i--) {
            if (dist[i] == n
                || (i < n - 1 && dist[i + 1] + 1 < dist[i]))
                    dist[i] = dist[i + 1] + 1;
        }

        return dist;
    }
};

Python Code

class Solution(object):
    def shortestToChar(self, s, c):
        """
        :type s: str
        :type c: str
        :rtype: List[int]
        """
        n = len(s)
        res = [0 if s[i] == c else None for i in range(n)]

        for i in range(1, n):
            if res[i] != 0 and res[i - 1] is not None:
                res[i] = res[i - 1] + 1

        for i in range(n - 2, -1, -1):
            if res[i] is None or res[i + 1] + 1 < res[i]:
                res[i] = res[i + 1] + 1
        return res

解法 4:窗口

思路

C 看成分界线,将 S 划分成一个个窗口。然后对每个窗口进行遍历,分别计算每个字符到窗口边界的距离最小值。

在这里插入图片描述

复杂度分析

  • 时间复杂度: O ( N ) O(N) O(N),N 是 S 的长度。
  • 空间复杂度: O ( 1 ) O(1) O(1)

代码 (JS/C++/Python)

JavaScript Code

/**
 * @param {string} S
 * @param {character} C
 * @return {number[]}
 */
var shortestToChar = function (S, C) {
  // 窗口左边界,如果没有就初始化为 Infinity,初始化为 S.length 也可以
  let l = S[0] === C ? 0 : Infinity,
    // 窗口右边界
    r = S.indexOf(C, 1);

  const res = Array(S.length);

  for (let i = 0; i < S.length; i++) {
    // 计算字符到当前窗口左右边界的最小距离
    res[i] = Math.min(Math.abs(i - l), Math.abs(r - i));

    // 遍历完了当前窗口的字符后,将整个窗口右移
    if (i === r) {
      l = r;
      r = S.indexOf(C, l + 1);
    }
  }

  return res;
};

C++ Code

class Solution {
public:
    vector<int> shortestToChar(string S, char C) {
        int n = S.length();

        int l = S[0] == C ? 0 : n;
        int r = S.find(C, 1);

        vector<int> dist(n);

        for (int i = 0; i < n; i++) {
            dist[i] = min(abs(i - l), abs(r - i));
            if (i == r) {
                l = r;
                r = S.find(C, r + 1);
            }
        }

        return dist;
    }
};

Python Code

class Solution(object):
    def shortestToChar(self, s, c):
        """
        :type s: str
        :type c: str
        :rtype: List[int]
        """
        n = len(s)
        res = [0 for _ in range(n)]

        l = 0 if s[0] == c else n
        r = s.find(c, 1)

        for i in range(n):
            res[i] = min(abs(i - l), abs(r - i))
            if i == r:
                l = r
                r = s.find(c, l + 1)
        return res

总结

以上就是本文所有内容了,希望能对你有所帮助,能够解决算法-字符的最短距离问题。

如果你喜欢本文,也请务必点赞、收藏、评论、转发,这会对我有非常大的帮助。请我喝杯冰可乐也是极好的!

已完结,欢迎持续关注。下次见~

附件

资源下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1171947.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里大佬:DDD 领域层,该如何设计?

说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 谈谈你的DDD落地经验&#xff1f; 谈谈你对DDD的理解&#xff1f…

C语言 程序环境 编译和链接

目录 1.程序的翻译环境和执行环境 2.详解C语言程序的编译和链接 2.1翻译环镜 2.2翻译的几个阶段 2.2.1预编译 2.2.2编译 词法分析 符号汇总 2.2.3汇编 生成符号表 2.3链接 1.合并段表 2.合并符号表和重定位 2.4运行环境 1.程序的翻译环境和执行环境 在ANSI C的任…

【网络协议】聊聊DNS协议如何域名解析和负载均衡

DNS 服务器 我们知道如果使用IP地址进行访问网站&#xff0c;很难进行记忆&#xff0c;所以DNS的作用是将域名转换成对应的IP地址。如果全世界都使用同一台DNS服务器&#xff0c;那么DNS服务器本身需要保证服务的高可用、高性能&#xff0c;以及分布式等。最好的方式就是分层。…

日语动词三分类

所有的动词原形都是由う段结尾 50音图 一类动词 一类动词又称五段动词&#xff08;う段动词&#xff09; 1.结尾是う段非る的动词 ます形规律&#xff1a;う段 > 同行的い段ます 日文平假名ます形中文書くかく書きます写探すさがす探します寻找勝つかつ勝ちます胜利遊ぶ…

电脑报错找不到msvcp120dll,无法继续执行代码,怎么修复?

在运行电脑软件时&#xff0c;出现msvcp120.dll丢失&#xff0c;我深知大家在遇到这种电脑问题时所感受到的无助和焦虑。今天&#xff0c;我就来为大家分享一下关于msvcp120.dll文件缺失的4个一键解决方法&#xff0c;希望能帮助到大家。 首先&#xff0c;我们要明确什么是msv…

SecoClient连接报错:接收返回码超时

话接上回&#xff0c;SecoClient能打开了&#xff0c;但是出现了连接时的错误 这次针对接收返回码超时进行解决。 参考连接&#xff1a; https://www.cnblogs.com/Crazy-Liu/p/14700121.html https://blog.csdn.net/lnigluan511513/article/details/1280683581、正常我们进入…

【JVM】双亲委派机制、打破双亲委派机制

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 JVM 一、双亲委派机制1.1 双亲委派的作用1.…

Jetpack:028-Jetpack中的Card

文章目录 1. 概念介绍2. 使用方法2.1 主要类型2.2 其它类型 3. 示例代码4. 内容总结 我们在上一章回中介绍了Jetpack中Switch相关的内容&#xff0c;本章回中 主要介绍Card。闲话休提&#xff0c;让我们一起Talk Android Jetpack吧&#xff01; 1. 概念介绍 我们在本章回中介…

linux的shell script判断用户输入的字符串,判断主机端口开通情况

判断输入的字符串是否是hello 图一运行报错 检查发下&#xff0c;elif 判断里面少个引号&#xff0c;哎&#xff0c;现在小白到了&#xff0c;一看就会&#xff0c;一写就错的时候了&#xff0c;好像现在案例比较简单&#xff0c;行数较少。 案例二 if 结合test 判断主机端…

企业电脑屏幕监控有哪些?如何实现电脑屏幕监控

企业电脑屏幕监控有哪些&#xff1f;如何实现电脑屏幕监控 下载使用安企神电脑屏幕监控软件 企业电脑屏幕监控是一种监测和记录员工在工作时间内在他们的计算机上执行的活动的技术。这种监控可以有多种目的&#xff0c;包括确保员工的生产力、确保数据安全性&#xff0c;或满…

CCLINK IEFB总线转ETHERNET/IP网络的协议网关使欧姆龙和三菱的数据互通的简单配置方法

想要实现CCLINK IEFB总线和ETHERNET/IP网络的数据互通。 捷米JM-EIP-CCLKIE是一款ETHERNET/IP从站功能的通讯网关&#xff0c;该产品主要功能是实现CCLINK IEFB总线和ETHERNET/IP网络的数据互通。本网关连接到ETHERNET/IP总线和CCLINK IEFB总线上都可以做为从站使用。网关分别…

静态链表的定义与实现(数据结构与算法)

1. 静态链表 用数组的方式实现的链表 单链表&#xff1a; 各个结点在内存中星罗棋布、散落天涯 静态链表&#xff1a;分配一整片连续的内存空间&#xff0c; 各个结点集中安置。 1.1 静态链表的优点 不需要像动态链表那样频繁地进行内存分配和释放&#xff0c;可以节省内存…

产品手册应该如何组织内容,以便用户能够快速找到所需信息?

产品手册应该如何组织内容&#xff0c;以便用户能够快速找到所需信息&#xff1f;这是一个关乎用户体验和产品文档效力的重要问题。当用户需要了解产品的功能、操作指南或故障排除时&#xff0c;他们希望能够轻松地找到准确、清晰的信息&#xff0c;而不是在冗长的手册中迷失方…

C语言——选择排序

完整代码&#xff1a; //选择排序 // 选择排序是一种简单直观的排序算法。它的工作原理如下:首先在未排序序列中找到最小&#xff08;大&#xff09;元素&#xff0c;存放到排序序列的起始位置&#xff0c;然后&#xff0c;再从剩余未排序元素中继续寻找最小&#xff08;大&am…

Rocky 安装jdk17

1&#xff09;检测jdk是否安装&#xff1a; #运行 java -version如果提示安装&#xff0c;则输入N&#xff0c;跳过 2&#xff09;检测cpu 类型 若未安装查看linux处理器架构&#xff1a; #运行 hostnamectl #或运行 arch 3&#xff09;去官网下载相应的编译版本的Jdk Or…

[SSD综述1.7] SSD接口形态: SATA、M.2、U.2、PCIe、BGA

依公知及经验整理,原创保护,禁止转载。 专栏 《SSD入门到精通系列》 <<<< 返回总目录 <<<< 前言 犹记得当年Windows 7系统体验指数中,那5.9分磁盘分数,在其余四项的7.9分面前,似乎已经告诉我们机械硬盘注定被时代淘汰。势如破竹的SSD固态硬盘,彻…

万岳讲堂:抖音小程序开发入门指南

抖音小程序可以将开发者的创意带入这个热门的应用中。本文将带您深入了解抖音小程序的开发入门指南&#xff0c;帮助您开始在这一平台上构建自己的应用。 一、什么是抖音小程序&#xff1f; 抖音小程序是一种轻量级的应用程序&#xff0c;它可以在抖音中直接运行&#xff0c;无…

记录腾讯云重置密码之后ssh就连不上的踩坑

腾讯云轻量级服务器SSH连不上 解决方案在最后&#xff0c;点我跳转 问题背景&#xff1a; 首先ssh ubuntu用户我是能用xshell带上密钥正常连接的 其次我重置了root密码&#xff0c;自己改了一个root密码&#xff0c;因为我要用root账号使用ftp传输文件 然后重置密码之后&…

【Unity细节】生命函数Start的逻辑比从外部调用方法比起来哪个快

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 &#x1f636;‍&#x1f32b;️收录于专栏&#xff1a;unity细节和bug &#x1f636;‍&#x1f32b;️优质专栏 ⭐【…

解决vmware安装ubuntu虚拟机显示不全以及无法实现windows与虚拟机之间无法相互复制粘贴问题

01、存在问题 02、解决方案 sudo apt-get autoremove open-vm-tools sudo apt-get install open-vm-tools sudo apt-get install open-vm-tools-desktop reboot //重启在这里插入图片描述 存在Bug 如果遇到一下问题&#xff0c;请先执行下列命令&#xff0c;然后…