代码随想录算法训练营Day09|151.翻转字符串里的单词|卡码网:55.右旋转字符串| 字符串:总结篇

题目1 :151.翻转字符串里的单词

在这里插入图片描述
题目要求:不要使用辅助空间,空间复杂度要求为O(1)

解题思路
1.移除多余空格
2.将整个字符串反转
3.将每个单词反转

移除多余空格

void removeExtraSpaces(string& s) {
    for (int i = s.size() - 1; i > 0; i--) {
        if (s[i] == s[i - 1] && s[i] == ' ') {
            s.erase(s.begin() + i);
        }
    }
    // 删除字符串最后面的空格
    if (s.size() > 0 && s[s.size() - 1] == ' ') {
        s.erase(s.begin() + s.size() - 1);
    }
    // 删除字符串最前面的空格
    if (s.size() > 0 && s[0] == ' ') {
        s.erase(s.begin());
    }
}

逻辑很简单,从前向后遍历,遇到空格了就erase。
但一个erase本来就是O(n)的操作。
erase操作上面还套了一个for循环,那么以上代码移除冗余空格的代码复杂度为O(n^2)。
那么如果使用双指针法来移除空格,最后resize(重新设置)一下字符串的大小,就可以做到O(n)的时间复杂度。

class Solution {
   /**
     * 不使用Java内置方法实现
     * <p>
     * 1.去除首尾以及中间多余空格
     * 2.反转整个字符串
     * 3.反转各个单词
     */
    public String reverseWords(String s) {
        // System.out.println("ReverseWords.reverseWords2() called with: s = [" + s + "]");
        // 1.去除首尾以及中间多余空格
        StringBuilder sb = removeSpace(s);
        // 2.反转整个字符串
        reverseString(sb, 0, sb.length() - 1);
        // 3.反转各个单词
        reverseEachWord(sb);
        return sb.toString();
    }

    private StringBuilder removeSpace(String s) {
        // System.out.println("ReverseWords.removeSpace() called with: s = [" + s + "]");
        int start = 0;
        int end = s.length() - 1;
        while (s.charAt(start) == ' ') start++;
        while (s.charAt(end) == ' ') end--;
        StringBuilder sb = new StringBuilder();
        while (start <= end) {
            char c = s.charAt(start);
            if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
                sb.append(c);
            }
            start++;
        }
        // System.out.println("ReverseWords.removeSpace returned: sb = [" + sb + "]");
        return sb;
    }

    /**
     * 反转字符串指定区间[start, end]的字符
     */
    public void reverseString(StringBuilder sb, int start, int end) {
        // System.out.println("ReverseWords.reverseString() called with: sb = [" + sb + "], start = [" + start + "], end = [" + end + "]");
        while (start < end) {
            char temp = sb.charAt(start);
            sb.setCharAt(start, sb.charAt(end));
            sb.setCharAt(end, temp);
            start++;
            end--;
        }
        // System.out.println("ReverseWords.reverseString returned: sb = [" + sb + "]");
    }

    private void reverseEachWord(StringBuilder sb) {
        int start = 0;
        int end = 1;
        int n = sb.length();
        while (start < n) {
            while (end < n && sb.charAt(end) != ' ') {
                end++;
            }
            reverseString(sb, start, end - 1);
            start = end + 1;
            end = start + 1;
        }
    }
}

1. 去除首尾和中间多余空格

  • 使用 removeSpace 方法。

  • 这一步生成一个清理后的 StringBuilder。

  • 实现细节:

    • 确定有效的起始和结束索引:
      start向右移动,直到遇到非空格字符
      end向左移动,直到遇到非空格字符

    • 遍历start 到end ,构造新的字符串:
      如果当前字符不是空格,直接加入StringBuilder
      如果当前字符是空格,则检查上一个字符是否为非空格,避免连续空格。

int start = 0;
int end = s.length() - 1;
while (s.charAt(start) == ' ') start++; // 去掉左侧空格
while (s.charAt(end) == ' ') end--;     // 去掉右侧空格

遍历start 到end :

StringBuilder sb = new StringBuilder();
        while (start <= end) {
            char c = s.charAt(start);//获取当前字符
            if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
                sb.append(c);
            }
            start++;
}
 if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
                sb.append(c);
}

该语句的条件含义:

  • c ! = ’ ’

    • 当前字符c不是空格,直接允许加入StringBuilder
    • 因为非空字符永远都是需要保留的
  • sb.charAt(sb.length() - 1) != ’ ’
    • 当前字符c是空格时,检查已经加入StringBuilder中的最后一个字符
    • 如果最后一个字符 不是空格 (sb.charAt(sb.length()-1) != ’ '),说明之前的字符是有效字符,当前的空格可以被加入。
    • 如果最后一个字符已经是空格,则跳过当前空格,防止连续空格出现

两者用 逻辑或 (||) 连接:

  • 如果 当前字符是非空格 (c != ’ '),无需检查 sb,直接加入。
  • 如果 当前字符是空格,则必须检查 StringBuilder
    的最后一个字符是否也是空格。

2. 反转整个字符串

 /**
     * 反转字符串指定区间[start, end]的字符
     */
    public void reverseString(StringBuilder sb, int start, int end) {
        // System.out.println("ReverseWords.reverseString() called with: sb = [" + sb + "], start = [" + start + "], end = [" + end + "]");
        while (start < end) {
            char temp = sb.charAt(start);
            sb.setCharAt(start, sb.charAt(end));
            sb.setCharAt(end, temp);
            start++;
            end--;
        }
        // System.out.println("ReverseWords.reverseString returned: sb = [" + sb + "]");
    }
  // 2.反转整个字符串
        reverseString(sb, 0, sb.length() - 1);//传入参数

3.reverseEachWord方法

private void reverseEachWord(StringBuilder sb) {
        int start = 0;
        int end = 1;
        int n = sb.length();
        while (start < n) {
            while (end < n && sb.charAt(end) != ' ') {
                end++;
            }
            reverseString(sb, start, end - 1);
            start = end + 1;
            end = start + 1;
        }
    }
  • 使用 reverseString 方法。
    将字符串的某个区间 [start, end] 内的字符顺序反转。
    实现细节:
    使用双指针:左指针 start 和右指针 end。
    交换两端字符,然后向中间推进,直到 start >= end。

  • 反转每个单词

  • 使用 reverseEachWord 方法。
    在已经整体反转的字符串基础上,逐一反转每个单词。
    实现细节:

    • 使用两个指针:
    • start: 表示当前单词的起始位置。
    • end: 从 start 开始寻找单词的末尾(遇到空格或字符串末尾时停止)。
    • 找到单词的区间 [start, end-1],调用 reverseString 方法反转单词。
    • 更新 start 和end,继续处理下一个单词。

●卡码网:55.右旋转字符串
在这里插入图片描述

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = Integer.parseInt(in.nextLine());
        String s = in.nextLine();

        int len = s.length();  //获取字符串长度
        char[] chars = s.toCharArray();
        reverseString(chars, 0, len - 1);  //反转整个字符串
        reverseString(chars, 0, n - 1);  //反转前一段字符串,此时的字符串首尾尾是0,n - 1
        reverseString(chars, n, len - 1);  //反转后一段字符串,此时的字符串首尾尾是n,len - 1
        
        System.out.println(chars);

    }

    public static void reverseString(char[] ch, int start, int end) {
        //异或法反转字符串,参照题目 344.反转字符串的解释
        while (start < end) {
            ch[start] ^= ch[end];
            ch[end] ^= ch[start];
            ch[start] ^= ch[end];
            start++;
            end--;
        }
    }
}

abcdefg---------(整体反转)---------> gf edcba ------- (局部反转)------>fg abcde
将字符串分成两部分

  • 前部分(索引[0,n-1])

  • 后部分(索引[n,len - 1])
    通过以下步骤实现:

  • 反转整个字符串

  • 再反转前一部分

  • 再反转后一部分

  • 输出完成分段反转后的字符串

代码解析

  1. 输入读取:
Scanner in = new Scanner(System.in);
int n = Integer.parseInt(in.nextLine());
String s = in.nextLine();

使用 Scanner 获取用户输入。
读取整数 n(分割点),以及字符串 s。

  1. 字符串长度和字符数组:
int len = s.length();  // 获取字符串长度
char[] chars = s.toCharArray();  // 将字符串转换为字符数组

将字符串转为字符数组方便处理,因为 Java 中字符串是不可变的,而字符数组可以直接修改。

  1. 字符串分段反转
reverseString(chars, 0, len - 1);  // 反转整个字符串
reverseString(chars, 0, n - 1);  // 反转前一段
reverseString(chars, n, len - 1);  // 反转后一段
  • 第一步: 反转整个字符串,形成一个基准的倒序排列。
  • 第二步: 反转前半部分,使其回到原始顺序。
  • 第三步: 反转后半部分,使其回到原始顺序。
  1. 输出结果:
System.out.println(chars);
//将处理后的字符数组转化为字符串输出。
  1. reverseString 方法
    反转字符数组指定区间 [start, end] 的内容。
    使用了异或交换的方式,原地进行反转,不需要额外空间。
while (start < end) {
    ch[start] ^= ch[end];  // 异或交换第一步
    ch[end] ^= ch[start];  // 异或交换第二步
    ch[start] ^= ch[end];  // 异或交换第三步
    start++;  // 左指针右移
    end--;    // 右指针左移
}

●字符串总结

字符串是若干字符组成的有限序列,也可以理解为是一个字符数组
双指针法在数组,链表和字符串中很常用。

●双指针回顾

### 关于代码随想录 Day04 的学习资料与解析 #### 一、Day04 主要内容概述 代码随想录 Day04 的主要内容围绕 **二叉树的遍历** 展开,包括前序、中序和后序三种遍历方式。这些遍历可以通过递归实现,也可以通过栈的方式进行迭代实现[^1]。 #### 二、二叉树的遍历方法详解 ##### 1. 前序遍历(Pre-order Traversal) 前序遍历遵循访问顺序:根节点 -> 左子树 -> 右子树。以下是基于递归的实现: ```python def preorderTraversal(root): result = [] def traversal(node): if not node: return result.append(node.val) # 访问根节点 traversal(node.left) # 遍历左子树 traversal(node.right) # 遍历右子树 traversal(root) return result ``` 对于迭代版本,则可以利用显式的栈来模拟递归过程: ```python def preorderTraversal_iterative(root): stack, result = [], [] current = root while stack or current: while current: result.append(current.val) # 访问当前节点 stack.append(current) # 将当前节点压入栈 current = current.left # 转向左子树 current = stack.pop() # 弹出栈顶元素 current = current.right # 转向右子树 return result ``` ##### 2. 中序遍历(In-order Traversal) 中序遍历遵循访问顺序:左子树 -> 根节点 -> 右子树。递归实现如下: ```python def inorderTraversal(root): result = [] def traversal(node): if not node: return traversal(node.left) # 遍历左子树 result.append(node.val) # 访问根节点 traversal(node.right) # 遍历右子树 traversal(root) return result ``` 迭代版本同样依赖栈结构: ```python def inorderTraversal_iterative(root): stack, result = [], [] current = root while stack or current: while current: stack.append(current) # 当前节点压入栈 current = current.left # 转向左子树 current = stack.pop() # 弹出栈顶元素 result.append(current.val) # 访问当前节点 current = current.right # 转向右子树 return result ``` ##### 3. 后序遍历(Post-order Traversal) 后序遍历遵循访问顺序:左子树 -> 右子树 -> 根节点。递归实现较为直观: ```python def postorderTraversal(root): result = [] def traversal(node): if not node: return traversal(node.left) # 遍历左子树 traversal(node.right) # 遍历右子树 result.append(node.val) # 访问根节点 traversal(root) return result ``` 而迭代版本则稍复杂一些,通常采用双栈法或标记法完成: ```python def postorderTraversal_iterative(root): if not root: return [] stack, result = [root], [] while stack: current = stack.pop() result.insert(0, current.val) # 插入到结果列表头部 if current.left: stack.append(current.left) # 先压左子树 if current.right: stack.append(current.right) # 再压右子树 return result ``` #### 三、补充知识点 除了上述基本的二叉树遍历外,Day04 还可能涉及其他相关内容,例如特兰数的应用场景以及组合问题的基础模板[^2][^4]。如果遇到具体题目,可以根据实际需求调用相应算法工具。 --- ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值