算法DAY9 | 28.找出字符串中第一个匹配项的下标 / 459.重复的子字符串 / 字符串总结 / 双指针回顾

本文总结了字符串处理中的关键问题,如找出字符串第一个匹配项的下标、重复子字符串的查找,探讨了Python与Java在字符串操作上的差异,并详细阐述了双指针在反转字符串、替换空格、数组元素移除、链表操作等场景中的应用,旨在提升算法理解和代码实现能力。
摘要由CSDN通过智能技术生成

28.找出字符串中第一个匹配项的下标

题目链接

  • 标签:字符串、KMP
  • 难度:7.0

关注后续更新。请添加图片描述


459.重复的子字符串

题目链接

  • 标签:字符串、KMP
  • 难度:7.0

关注后续更新。


字符串总结

  1. 字符串和字符数组的灵活选择

    这取决于语言的特性。比如Python的String类就很好用,API众多;相比而言Java就连取字符都要用charAt()函数,不能直接通过下标取某一字符。Java的数组操作也更方便,例如增强for循环,可以快速遍历数组元素,所以在Java里用字符数组的频率就比Python高

    除此之外,Java还有StringBuilder和StringBuffer这两大可变长字符串类型,需要在字符串处理的题目中灵活运用,区别是一个线程不安全,一个线程安全,单线程的时候选用StringBuilder就行

  2. 要不要使用库函数

    基础题不用库函数。

    如果库函数仅仅是解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数。

  3. 字符串中的双指针

    字符串本质是字符数组,或者说可以转化成字符数组(s.toCharArray()),也可以转回来(new String(char[])),所以字符串的双指针本质就是数组的双指针。

    在反转字符串中,我们从前后分别遍历left、right指针,借助临时变量或用位运算完成交换操作。

    在替换空格中,我们在给数组预先扩容后,用快慢两个指针一个在前面遍历,一个在后面填坑,类似删除元素那道题。

  4. 反转字符串

    是字符串最经典的一类题,基本靠的是手动模拟,结合双指针等算法思想,考察代码能力。

    例题与讲解:541.反转字符串II151.翻转字符串里的单词


双指针回顾

  1. 移除元素

    快慢指针,一个在前面遍历,确定是不是要移除的元素,如果不是,慢指针在后面填坑。

public int removeElement(int[] nums, int val) {
    // 快慢指针
    int slowIndex = 0;
    for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
        if (nums[fastIndex] != val) {
            nums[slowIndex] = nums[fastIndex];
            slowIndex++;
        }
    }
    return slowIndex;
}
  1. 反转字符串

    从前后分别遍历left、right指针,借助临时变量或用位运算完成交换操作。

public void reverseString(char[] s) {
    int l = 0;
    int r = s.length - 1;
    while(l < r){
        char temp = s[l];
        s[l] = s[r];
        s[r] = temp;
        l++;
        r--;
    }
}
  1. 替换空格

    先扩容,用快慢两个指针一个在前面遍历,一个在后面填坑。

public String replaceSpace(String s) {
    if(s == null || s.length() == 0){
        return s;
    }
    //扩充空间,空格数量2倍
    StringBuilder str = new StringBuilder();
    for (int i = 0; i < s.length(); i++) {
        if(s.charAt(i) == ' '){
            str.append("  ");
        }
    }
    //若是没有空格直接返回
    if(str.length() == 0){
        return s;
    }
    //有空格情况 定义两个指针
    int left = s.length() - 1;//左指针:指向原始字符串最后一个位置
    s += str.toString();
    int right = s.length()-1;//右指针:指向扩展字符串的最后一个位置
    char[] chars = s.toCharArray();
    while(left>=0){
        if(chars[left] == ' '){
            chars[right--] = '0';
            chars[right--] = '2';
            chars[right] = '%';
        }else{
            chars[right] = chars[left];
        }
        left--;
        right--;
    }
    return new String(chars);
}
  1. 翻转字符串里的单词

    151.翻转字符串里的单词

  2. 反转链表

    两个指针用于遍历,还有一个临时指针防断链。

public ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode cur = head;
    ListNode temp = null;
    while (cur != null) {
        temp = cur.next;// 保存下一个节点
        cur.next = prev;
        prev = cur;
        cur = temp;
    }
    return prev;
}
  1. 删除链表的倒数第N个结点

    先让快指针走N个,再一起遍历到终点。

    注意循环的边界条件,最好画个图防止写错。

    适时引入虚拟头结点。

public ListNode removeNthFromEnd(ListNode head, int n){
    ListNode dummyNode = new ListNode(0);
    dummyNode.next = head;

    ListNode fastIndex = dummyNode;
    ListNode slowIndex = dummyNode;

    //只要快慢指针相差 n 个结点即可
    for (int i = 0; i < n  ; i++){
        fastIndex = fastIndex.next;
    }

    while (fastIndex.next != null){
        fastIndex = fastIndex.next;
        slowIndex = slowIndex.next;
    }

    //此时 slowIndex 的位置就是待删除元素的前一个位置。
    //具体情况可自己画一个链表长度为 3 的图来模拟代码来理解
    slowIndex.next = slowIndex.next.next;
    return dummyNode.next;
}
  1. 链表相交

    先用两个指针遍历,记录下两条链表的长度。让长的链表先走一点,让他们回到同一起跑线,在一起往后遍历,直到找到相同结点。

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    ListNode curA = headA;
    ListNode curB = headB;
    int lenA = 0, lenB = 0;
    while (curA != null) { // 求链表A的长度
        lenA++;
        curA = curA.next;
    }
    while (curB != null) { // 求链表B的长度
        lenB++;
        curB = curB.next;
    }
    curA = headA;
    curB = headB;
    // 让curA为最长链表的头,lenA为其长度
    if (lenB > lenA) {
        //1. swap (lenA, lenB);
        int tmpLen = lenA;
        lenA = lenB;
        lenB = tmpLen;
        //2. swap (curA, curB);
        ListNode tmpNode = curA;
        curA = curB;
        curB = tmpNode;
    }
    // 求长度差
    int gap = lenA - lenB;
    // 让curA和curB在同一起点上(末尾位置对齐)
    while (gap-- > 0) {
        curA = curA.next;
    }
    // 遍历curA 和 curB,遇到相同则直接返回
    while (curA != null) {
        if (curA == curB) {
            return curA;
        }
        curA = curA.next;
        curB = curB.next;
    }
    return null;
}
  1. 三数之和

    双指针在这里是为了降低时间复杂度,从O(n3)降到O(n2)。

    先排序,在定下一个元素之后,另外两个指针分别从前后开始遍历,就不用开两个循环了。

    注意去重的实现。

public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> result = new ArrayList<>();
    Arrays.sort(nums);
    // 找出a + b + c = 0
    // a = nums[i], b = nums[left], c = nums[right]
    for (int i = 0; i < nums.length; i++) {
        // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
        if (nums[i] > 0) { 
            return result;
        }

        if (i > 0 && nums[i] == nums[i - 1]) {  // 去重a
            continue;
        }

        int left = i + 1;
        int right = nums.length - 1;
        while (right > left) {
            int sum = nums[i] + nums[left] + nums[right];
            if (sum > 0) {
                right--;
            } else if (sum < 0) {
                left++;
            } else {
                result.add(Arrays.asList(nums[i], nums[left], nums[right]));
                // 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
                while (right > left && nums[right] == nums[right - 1]) right--;
                while (right > left && nums[left] == nums[left + 1]) left++;

                right--; 
                left++;
            }
        }
    }
    return result;
}
  1. 四数之和

    在三数之和外面再套一层循环,也就是用两个循环定下两个元素,再用双指针。

public List<List<Integer>> fourSum(int[] nums, int target) {
    List<List<Integer>> result = new ArrayList<>();
    Arrays.sort(nums);

    for (int i = 0; i < nums.length; i++) {

        // nums[i] > target 直接返回, 剪枝操作
        if (nums[i] > 0 && nums[i] > target) {
            return result;
        }

        if (i > 0 && nums[i - 1] == nums[i]) {    // 对nums[i]去重
            continue;
        }

        for (int j = i + 1; j < nums.length; j++) {

            if (j > i + 1 && nums[j - 1] == nums[j]) {  // 对nums[j]去重
                continue;
            }

            int left = j + 1;
            int right = nums.length - 1;
            while (right > left) {
                // nums[k] + nums[i] + nums[left] + nums[right] > target int会溢出
                long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
                if (sum > target) {
                    right--;
                } else if (sum < target) {
                    left++;
                } else {
                    result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                    // 对nums[left]和nums[right]去重
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;

                    left++;
                    right--;
                }
            }
        }
    }
    return result;
}
总结

除了链表一些题目一定要使用双指针,其他题目都是使用双指针来提高效率,一般是将O(n2)的时间复杂度降为O(n)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值