sisterAn/JavaScript-Algorithms

leetcode1209:删除字符串中的所有相邻重复项 II

sisterAn opened this issue · 11 comments

给你一个字符串 s,「k 倍重复项删除操作」将会从 s 中选择 k 个相邻且相等的字母,并删除它们,使被删去的字符串的左侧和右侧连在一起。

你需要对 s 重复进行无限次这样的删除操作,直到无法继续为止。

在执行完所有删除操作后,返回最终得到的字符串。

本题答案保证唯一。

示例 1:

输入:s = "abcd", k = 2
输出:"abcd"
解释:没有要删除的内容。

示例 2:

输入:s = "deeedbbcccbdaa", k = 3
输出:"aa"
解释: 
先删除 "eee"  "ccc",得到 "ddbbbdaa"
再删除 "bbb",得到 "dddaa"
最后删除 "ddd",得到 "aa"

示例 3:

输入:s = "pbbcggttciiippooaais", k = 2
输出:"ps"

提示:

  • 1 <= s.length <= 10^5
  • 2 <= k <= 10^4
  • s 中只含有小写英文字母。

附赠 leetcode 地址:leetcode

var removeDuplicates = function(s, k) {
    let stack = [];
    for(let _s of s){
        const last = stack.pop();
        if(!last || last[0] !== _s){
            stack.push(last)
            stack.push(_s)
        }else if(last.length < k-1){
            stack.push(last + _s)
        }
    }

    return stack.join('')
};
function delStr(s = '', k = 2) {
            if (typeof s !== 'string' || k < 2) {
                return;
            }
            let c = new RegExp(`(.)\\1{${k-1}}`, 'g');
            while (s !== s.replace(c, '')) {
                s = s.replace(c, '');
            }

            return s;
     }

原理跟昨天的题是一样的,不过昨天的题是一次入栈一个,今天是k个。那我们就让它每次入栈 k-1 个,站头的 k-1 个元素的第一个如果与当前准备入栈的元素相同,则代表k个元素是一样的,则无需入栈。

var removeDuplicates = function(s, k) {
    let stack = [];
    for(let c of s){
        const prev = stack.pop();
        if(!prev || prev[0] !== c){
            stack.push(prev)
            stack.push(c)
        }else if(prev.length < k-1){
            stack.push(prev + c)
        }
    }
    return stack.join('')
};

解答:利用栈

解题**: 遍历字符串依次入栈,入栈时,判断当前元素与栈头元素是否一致,如果不一致则入栈,如果一致,判断栈头字符是否长度为 k - 1 ,如果为 k-1 ,即加入该字符时,满足连续相同字符 k 个,此时,需要栈头出栈,当前字符不进栈,如果小于 k-1 ,则取出栈头字符,加上当前字符,重新入栈

代码实现:

const removeDuplicates = function(s, k) {
    let stack = []
    for(let c of s) {
        let prev = stack.pop()
        if(!prev || prev[0] !== c) {
            stack.push(prev)
            stack.push(c)
        } else if(prev.length < k-1) {
            stack.push(prev+c)
        }
    }
    return stack.join('')
};

时间复杂度:O(n)

空间复杂度:O(n)

leetcode

暴力来一波

var removeDuplicates = function(s, k) {
    function findRepeatPos (s, k) {
        for (let i = 0; i < s.length - k + 1; i++) {
            let flag = true
            for (let j = i; j < i + k - 1; j++) {
                if (s[j] !== s[j + 1]) {
                    flag = false
                    break
                }
            }
            if (flag === true) {
                return [i, i + k - 1]
            }
        }
        return [-1]
    }

    while (true) {
        let [start, end] = findRepeatPos(s, k)
        console.log(start, end)
        if (start === -1) {
            return s
        } else {
            s = s.slice(0, start) + s.slice(end + 1)
        }
    }
    return s
};
function delStr(s = '', k = 2) {
            if (typeof s !== 'string' || k < 2) {
                return;
            }
            let c = new RegExp(`(.)\\1{${k-1}}`, 'g');
            while (s !== s.replace(c, '')) {
                s = s.replace(c, '');
            }

            return s;
     }

这种做法可以,但是它采用循环匹配替换,花费时间较长,需要考虑一下时间复杂度问题

var arr = 'pbbcggttciiippooaais'.split(''), k = 2;
var startIndex = 0;
while(startIndex < arr.length) {
var num = 1;
while(arr[startIndex + num] === arr[startIndex] && num < k) {
num++;
}
if (num === k) {
arr.splice(startIndex, num);
startIndex = Math.max(0, startIndex - k + 1);
} else {
startIndex++;
}
}

采用两个栈来进行:
function removeDuplicateK(s, k) {
    if(k <= 0) {
        return s;
    }   
    let stack1 = new Stack();
    for(let i=0; i<s.length; i++) {
        let iValue = s[i]
            stack2 = new Stack(),
            j = 0,
            isValid = true;
        while(j < k-1) {
            let popValue = stack1.pop();
            if(popValue !== null){
                stack2.push(popValue);
            }
            if(popValue === null || popValue !== iValue) {
                isValid = false;
                break;
            }
            j += 1;
        }
        if(!isValid) {
            while(stack2.length > 0) {
                stack1.push(stack2.pop());
            }
            stack1.push(iValue);
        }
    }
    return stack1.dataStore.join('');
}

正则 + 递归

var removeDuplicates = function(s, k) {
  if (s.length < k) {
    return s
  }

  const repeatLenStrRe = new RegExp(`(.)\\1{${ k - 1 }}`, 'g')
  const replaceStr = s.replace(repeatLenStrRe, '')
  // 说明没有需要再匹配的了
  if (replaceStr === s) {
    return replaceStr
  } else {
    return removeDuplicates(replaceStr, k)
  }
};

大家来评评理。对于以下测试用例,我输出“is”有什么不对吗

"pbbcggttciiippooaais"
2

预期结果
"ps"

下面是我的代码

var removeDuplicates = function(s, k) {
  for (let left = 0; i < s.length - k + 1; left++) {
    let right = i;
    while (s[right + 1] == s[left]) {
      right = right + 1;
    }

    const gap = right - left + 1;
    if (gap >= k) {
      s = s.substr(0, i) + s.substr(i + gap, s.length)
      left = -1
    }
  }
  return s
};

执行过程输出

pbbcggttciiippooaais -->  pcggttciiippooaais
pcggttciiippooaais -->  pcttciiippooaais
pcttciiippooaais -->  pcciiippooaais
pcciiippooaais -->  piiippooaais
piiippooaais -->  pppooaais
pppooaais -->  ooaais
ooaais -->  aais
aais -->  is

和删除相邻重复项是同一个类型,利用栈来存储扫描过的字符,不一样的是该题支持 k 个,这个理论上可以按照上一题一样的原则,直接推栈就好了,但是出栈的时候,考虑到 假如 k 值很大,会做很多重复性的 pop 工作,所以我这里 将 栈的节点从单纯存储读取的字符, 改为 一个 对象,对象保存了读取的字符,和已经读取的字符数量,这样每次 pop 只需要一次即可完成任务

interface StackNode {
    value: string;
    count: number;
}
const removeDuplicates = (source: string, k: number) => {
    if (source.length <= 0 || k <= 0 || source.length < k) return source;
    const stack: StackNode[] = [];
    for (let i = 0; i < source.length; i++) {
        const char = source[i];
        if (stack.length <= 0) {
            stack.push({ value: char, count: 1 });
            continue;
        }
        const lastStackNode = stack[stack.length - 1];
        if (lastStackNode.value !== source[i]) {
            stack.push({ value: char, count: 1 });
            continue;
        }
        if (lastStackNode.count === (k - 1)) {
            stack.pop();
        } else {
            lastStackNode.count++;
        }
    }
    return stack.reduce((prev, cur) => prev + cur.value.repeat(cur.count), '');
};