【数据结构与算法】-(8.1)字符串去重算法


【数据结构与算法】-(1)基础篇

【数据结构与算法】-(2)线性表基础

【数据结构与算法】-(3)循环链表(单向)

【数据结构与算法】-(4)双向链表和双向循环链表

【数据结构与算法】-(5)链表面试题解析

【数据结构与算法】-(6)栈

【数据结构与算法】-(7)队列

【数据结构与算法】-(8)栈之算法题

去除重复字母

LeetCode 级别:困难

Q: 题目

给你一个仅包含小写字母的字符串,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证返回结果的字典序最小。要求:不能打乱其他字符的相对位置。原题目leetcode链接

示例1:

输入:"bcabc"

输出:"abc"

示例2:

输入:"cbacdcbc"

输出:"acdb" ~~cbad~~, ~~bacd~~, ~~adcb~~

Analyze:

分析关键字:字典序不打乱

  • 字典序最小:即排列顺序按照26个字母一次排序,比如给的antman 肯定要排在ultraman 前面
  • 不打乱排序:即去除重复字母后,未被重复的那部分字母,不改变原来位置。比如bcabc 结果是 abc 那么去除的是第1、第2位的bc, 后面3位abc 依然不变。

A:思路:

  1. 给定字符串 s ,排除字符串异常情况;

  2. 使用一个record 数组,来记录字符串中字母出现的次数;

  3. 创建一个栈stack ,用来存储去除重复字母的结果,并利用它的特性帮助我们找到正确的次序。

  4. 遍历给定的字符串s

  5. 从0~top 依次遍历 stack,判断当前的字符s[i] 是否存在于stack 中,用一个int类型的值isExist来表示是否存在。

    1. 如果isExsit 存在,即==1,将record [s[i]] 位置上出现次数减1,即record[s[i]]--,并继续遍历下一个字符;意思是,当前的stack 已经包含这个字符,以后都不需要继续处理这个字符了。

    2. 如果isExist 不存在,则需要while循环一下,找到这个字符正确的位置,然后存储起来。循环规则大概是这样:跳过栈中比当前字符大、而且后面还会出现的元素,将字符入栈。

      while 循环的条件如下:

      1. top > -1 来保证栈不为空
      2. stack[top] > s[i] 表示栈定元素比当前元素大
      3. record[stack[top]] > 1 表示后面还会出现
  6. 等遍历完所有的字符后,对当前的字符栈stack 添加一个结束符\0,并返回当前字符串首地址;

代码实现

char *removeDuplicateLetters(char *s)
{
/*
① 特殊情况处理,s为空,或者字符串长度为0;
② 特殊情况,s的长度为1,则没有必要后续的处理,则直接返回s;
*/
if (s == NULL || strlen(s) == 0) {
return "";
}
if (strlen(s) == 1) {
return s;
}

//record数组,用来记录字符串s中每个字符未来会出现的次数;
char record[26] = {0};
int len = (int)strlen(s);

//申请一个字符串stack;(用栈的特性来进行stack字符串的数据进出)
char* stack = (char*)malloc(len * 2 * sizeof(char));
//memset(void *s, int ch, size_t n) 将stack len*2*sizeof(char)长度范围的空间填充0;
memset(stack, 0, len * 2 * sizeof(char));
//stack 栈顶赋初值为-1;
int top = -1;

//1.统计每个字符的频次
//例如bcabc recod[26] = {1,2,2};
int i;
for (i = 0; i < len; i++) {
record[s[i] - 'a']++;
}

//2.遍历s,入栈
for (i = 0; i < len; i++) {


//isExist 标记, 判断当前字符是否存在栈中;
int isExist = 0;

//①从0~top,遍历stack 判断当前字符s[i]是否存在于栈stack中
//如果当前字符是否存在于栈的flag, 0表示不存在, 1表示存在
//top指向栈顶(也是执行stack字符串最后一个字符的位置,表示字符串长度上限)
for (int j = 0; j <= top; j++) {
if (s[i] == stack[j]) {
isExist = 1;
break;
}
}

//② 如果存在,record[s[i]]位置上的出现次数减一,并继续遍历下一个字符
//③ 如果不存在,则需要循环一个正确位置存储起来;
//④ 如果不存在,跳过栈中所有比当前字符大、且后面还会出现的元素,然后将当前字符入栈
// top > -1表示栈非空
//stack[top] > s[i]表示栈顶元素比当前元素大
//record[stack[top]] > 1表示后面还会出现
//例如b,c因为不符合以下条件会直接入栈.stack[] = "bc",但是当当前字符是"a"时,由于bcabc,a不应该是在stack的顺序是"bca",所以要把位置不符合的字符出栈;
//top = 1,stack[top] > s[i], c>a; 并且stack[top] 在之后还会重复的出现,所以我们可以安心的把stack中的栈顶C出栈,所以stack[]="b",top减一后等于0; 同时也需要将record[c]出现次数减一;
//top=0,stack[top]>s[i],b>a,并且stack[top] 在之后还会出现,所以stack把栈顶b出栈,所以此时栈stack[]="",top减一后等于-1, 此时栈中位置不正确的字符都已经移除;

if (isExist == 1) {
record[s[i] - 'a']--;
} else {
while (top > -1 && stack[top] > s[i] && record[stack[top] - 'a'] > 1) {

// 跳过该元素,频次要减一
record[stack[top] - 'a']--;
// 出栈
top--;
}

//⑤ 结束while 循环;
//循环结束的3种可能性:(1)移动到栈底(top == -1) ; (2)栈顶元素小于当前元素(stack[top] <= s[i]) (3)栈顶元素后面不出现(record[stack[top]] == 1)
// 此时,当前元素要插入到top的下一个位置
// top往上移动1位
top++;
// 入栈
stack[top] = s[i];
}
}

//结束栈顶添加字符结束符
stack[++top] = '\0';

return stack;
}

试着执行一下代码:

char *s ;
s = removeDuplicateLetters("zdzdaabc");

结果如下:

zdzdaabc = > dzabc ,符合要求 ✅

提交结果

leetcode 双百,可以可以


文章作者: 李佳
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 李佳 !
评论
 上一篇
【数据结构与算法】-(8.2)字符串搜索算法和RK&BP算法 【数据结构与算法】-(8.2)字符串搜索算法和RK&BP算法
【数据结构与算法】-(1)基础篇 【数据结构与算法】-(2)线性表基础 【数据结构与算法】-(3)循环链表(单向) 【数据结构与算法】-(4)双向链表和双向循环链表 【数据结构与算法】-(5)链表面试题解析 【数据结构与算法】-(6)栈
2020-04-22 李佳
下一篇 
【数据结构与算法】-(8)栈之算法题 【数据结构与算法】-(8)栈之算法题
【数据结构与算法】-(1)基础篇 【数据结构与算法】-(2)线性表基础 【数据结构与算法】-(3)循环链表(单向) 【数据结构与算法】-(4)双向链表和双向循环链表 【数据结构与算法】-(5)链表面试题解析 【数据结构与算法】-(6)栈
2020-04-20 李佳
  目录