算法学习:数组 - 双指针法

821. 字符的最短距离

给你一个字符串 s 和一个字符 c ,且 c 是 s 中出现过的字符。

返回一个整数数组 answer ,其中 answer.length == s.length 且 answer[i] 是 s 中从下标 i 到离它 最近 的字符 c 的 距离 。

两个下标 i 和 j 之间的 距离 为 abs(i - j) ,其中 abs 是绝对值函数。

背景:字符串

目的:计算与目的字符的距离,保留最短距离

方法:快慢指针

解题思路

暴力解法的思路是这样的:让慢指针找到目标字符c,快指针从下标0开始计算与字符c之间的距离。每找到一个字符c都要重新遍历一遍字符串,并保留最小值。这需要我们初始化一个数组保留我们的结果,并且数组的初始值为一个比较大的值。

直接上代码:

#include <vector>
#include <string>

using namespace std;

class Solution
{
public:
    vector<int> shortestToChar(string s, char c)
    {
        int length = s.length();
        vector<int> ans(length, INT16_MAX);
        for (int i = 0; i < length; i++)
        {
            for (int j = 0; s.at(i) == c && j < length; j++)
            {
                ans[j] = std::min(std::abs(i - j), ans[j]);
            }
        }
        return ans;
    }
};
int main(int argc, char const *argv[])
{
    Solution s;
    s.shortestToChar("loveleetcode", 'e');
    return 0;
}

两次遍历的思路:一个字符串,从左到右计算一遍最短距离,再从右到左计算一遍最短距离,两次遍历中最小的距离就是我们想要的结果。

使用这种思路,我们可以将复杂度从O(n^2)降低到O(n)。

我们需要一个指针 prev 来保留字符c出现的位置,再用一个指针 i 来遍历字符串。

从左向右遍历时,存储的结果为 i - prev,这样字符c右侧的距离就计算完毕了。在遍历之前,我们需要考虑prev的初始值,这关系到我们能否得到正确的结果。

从左向右遍历时,我们考虑不到字符c左侧的情况,但是我们希望字符c左侧的距离尽可能存储一个最大值,这样在进行从右向左的遍历中,这个最大值会得到正确的更新。

若要让i - prev最大,那么prev最好是一个负值,一个比较合适的负值就是字符串的长度,或者说只要大于这个长度就可以,让我们来验证一下这个想法,考虑一个极端情况:

s = "abcde", c = 'd'

我们让prev的初始值为字符串的长度5,那么经过i - prev之后的结果如何呢?

s = "abcde"
ans = [5,6,7,0,1]

‘abc’三个字符实际上的和‘d’距离,要比数组中记录的小,这样就达到了我们的目的–尽可能的存储一个最大值。

之后我们从右至左遍历,存储的结果为prev - i,这样字符c左侧的距离就计算完毕了,但我们仍然需要考虑prev的初始值。

和从左遍历相反,我们考虑不到c右侧的情况,同样的,我们希望c的右侧计算出一个最大值,这样在和前一次遍历的结果取最小值时,我们能得到正确的结果。

若要让prev - i最大,prev最好是一个极大值,只要让prev - i的结果大于字符串长度即可,这里我们将prev的初始值设为两倍字符串的长度,我们来验证一下这样的想法。

s = "abcde", c = 'a'
ans = [0,9,8,7,6]

上面是一次从右向左遍历的结果,在字符‘a’的右侧,我们确实得到了较大的值,这很好的符合了我们的预期。

最后,我们只需要保留两次遍历中最小的结果即可。

来看看代码:

#include <vector>
#include <string>

using namespace std;

class Solution
{
public:
    vector<int> shortestToChar(string s, char c)
    {
        int length = s.length();
        vector<int> ans(length);
        int prev = 0 - length;
        for (int i = 0; i < length; i++)
        {
            if (s.at(i) == c)
            {
                prev = i;
            }
            ans[i] = i - prev;
        }
        prev = 2 * length;
        for (int i = length - 1; i >= 0; i--)
        {
            if (s.at(i) == c)
            {
                prev = i;
            }
            ans[i] = std::min(prev - i, ans[i]);
        }

        return ans;
    }
};
int main(int argc, char const *argv[])
{
    Solution s;
    s.shortestToChar("loveleetcode", 'e');
    return 0;
}