字符串/数组去重

694 阅读2分钟

1. 祖马(连续地消除邻接的相同字符:aabbbacaa --> c)

思路:从左往右扫字符时,每遇到一个字符,在处理时都要往回看(进行对比),这种情况下栈的数据结构是很适合用的。如下图


方法一:

一个指针i(向后扫描),一个栈result(存放最终结果)

case 1: 如果str[i] !== result.top,直接把这个字符放进结果栈里面

case 2: 如果str[i] === result.top,指针继续往后移动,直到遇到跟栈顶不一样的,然后把栈顶弹出,这样就相当于联同中间的一样的全部一次剔除。但是不把这个新的加进去,而是i--去启动下一个循环,因为这个新的虽然跟它的直接前驱不同,但有可能跟你很久之前放进result的字符一样。

因为js的数组没有top或者peek方法去看栈顶元素,但是大家都知道array[array.length - 1]就是最后一个元素了。下面上代码:

function dedupRepeatedly(str) {
    const result = []; // 虽然是数组,但是是栈,嗯。。
    
    for (let i = 0; i < str.length; i++) {
        const top = result[result.length - 1]; // 看栈顶
        if (result.length > 0 && str[i] === top) {
            // 如果相同,则一直往后看
            let j = i + 1;
            for (; j < str.length; j++) {
                if (str[j] !== top) {
                    break; // 看到不同的才跳出
                }
            }
            
            result.pop(); // 弹出已经放入result的那个相同的
            i = j - 1; // 回退一格,让循环继续跟result栈顶比较
        } else {
            result.push(str[i]);
        }
    }

    return result.join('');
}

第一个判断那,有人说js就算result[-1]也不会越界报错,为啥还要多此一举?我觉得是可以但是总有点奇技淫巧感觉,毕竟就算你赋值result[-1]=2了之后result.length还是0,这个时候就不是当数组用了而是当object。

方法二:

不用单独声明一个栈,直接用两个快慢指针模仿栈。

慢指针slow总是指向(想象的)栈顶,所以str的0~slow间的值即为result,快指针i还是做他以前的工作。

function dedupRepeatedly(str) {
    str = str.split(''); // 首先把str变成数组,因为string是不可变的(immutable),后面str[i] = 'v'没用
    let slow = -1;    // 这我就直接-1开始了, 从0开始要考虑的情况比较多就算了
    for (let i = 0; i < str.length; i++) {
        const top = str[slow];
        if (str[i] === top) {
            let j = i + 1;
            for (; j < str.length; j++) {
                if (str[j] !== top) {
                    break;
                }
            }
            
            slow--; // 栈顶指针往回移一位,相当于pop了
            i = j - 1;
        } else {
            str[++slow] = str[i]; // 设置下一个栈顶元素
        }
    }

    return str.slice(0, slow + 1).join('');
}