Android学习总结之算法篇五(字符串)

字符串求回文字串数目

java 复制代码
public class CountPalindromicSubstrings {
    /**
     * 此方法用于计算字符串中回文子串的数量
     * @param s 输入的字符串
     * @return 回文子串的数量
     */
    public static int countSubstrings(String s) {
        // 若输入字符串为空或长度为 0,直接返回 0
        if (s == null || s.length() == 0) {
            return 0;
        }
        // 用于记录回文子串的数量
        int count = 0;
        // 获取字符串的长度
        int n = s.length();
        // 遍历字符串中的每个字符
        for (int i = 0; i < n; i++) {
            // 以单个字符为中心进行扩展,统计以该字符为中心的回文子串数量
            count += expandAroundCenter(s, i, i);
            // 以两个相邻字符为中心进行扩展,统计以这两个相邻字符为中心的回文子串数量
            count += expandAroundCenter(s, i, i + 1);
        }
        return count;
    }

    /**
     * 以给定的左右索引为中心向两边扩展,计算以该中心的回文子串数量
     * @param s 输入的字符串
     * @param left 左索引
     * @param right 右索引
     * @return 以该中心的回文子串数量
     */
    private static int expandAroundCenter(String s, int left, int right) {
        // 用于记录以该中心的回文子串数量
        int count = 0;
        // 当左右索引在字符串范围内,并且对应字符相等时,继续扩展
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            // 每找到一个回文子串,数量加 1
            count++;
            // 左索引向左移动一位
            left--;
            // 右索引向右移动一位
            right++;
        }
        return count;
    }

    public static void main(String[] args) {
        // 定义一个示例字符串
        String s = "abc";
        // 调用 countSubstrings 方法计算回文子串的数量,并输出结果
        System.out.println("回文子串的数目是: " + countSubstrings(s));
    }
}    

字符串转整数(atoi)

题目:输入一个表示整数的字符串,转换成整数并输出。需处理正负号、溢出(返回 INT_MAX 或 INT_MIN)、非法字符(遇到非数字字符停止转换)。

java 复制代码
// 该类用于将字符串转换为整数,模拟实现 Java 中的字符串转整数功能
public class StringToInteger {
    // 此方法将输入的字符串 s 转换为整数
    public int myAtoi(String s) {
        // 获取输入字符串的长度
        int len = s.length();
        // 若字符串长度为 0,即空字符串,直接返回 0
        if (len == 0) return 0;

        // 步骤 1: 去除字符串开头的空白字符
        // 初始化索引,用于遍历字符串
        int index = 0;
        // 当索引未越界且当前字符为空白字符时,索引向后移动
        while (index < len && s.charAt(index) == ' ') {
            index++;
        }

        // 步骤 2: 处理字符串中的符号位
        // 初始化符号,默认为正号
        int sign = 1;
        // 若索引未越界且当前字符为正号或负号
        if (index < len && (s.charAt(index) == '+' || s.charAt(index) == '-')) {
            // 若为负号,将符号设为 -1;若为正号,符号保持为 1
            sign = (s.charAt(index) == '-') ? -1 : 1;
            // 索引向后移动一位
            index++;
        }

        // 步骤 3: 把字符串中的数字部分转换为整数,同时处理可能的溢出情况
        // 用 long 类型存储结果,避免在转换过程中出现整数溢出
        long result = 0;
        // 当索引未越界且当前字符为数字时
        while (index < len && Character.isDigit(s.charAt(index))) {
            // 把当前字符转换为对应的数字
            int digit = s.charAt(index) - '0';
            // 把当前数字添加到结果中
            result = result * 10 + digit;

            // 检查是否发生正溢出
            if (sign == 1 && result > Integer.MAX_VALUE) {
                // 若发生正溢出,返回整数的最大值
                return Integer.MAX_VALUE;
            }
            // 检查是否发生负溢出
            if (sign == -1 && result > -(long)Integer.MIN_VALUE) {
                // 若发生负溢出,返回整数的最小值
                return Integer.MIN_VALUE;
            }
            // 索引向后移动一位
            index++;
        }
        // 把最终结果乘以符号,并转换为 int 类型返回
        return (int)(result * sign);
    }

    public static void main(String[] args) {
        // 创建 StringToInteger 类的实例
        StringToInteger solution = new StringToInteger();
        // 测试不同的输入字符串,并打印转换后的整数结果
        System.out.println(solution.myAtoi("42"));         // 输出 42
        System.out.println(solution.myAtoi("-42"));        // 输出 -42
        System.out.println(solution.myAtoi("4193 with words")); // 输出 4193
        System.out.println(solution.myAtoi("words and 987")); // 输出 0(遇到非数字字符则停止转换)
        System.out.println(solution.myAtoi("-91283472332")); // 输出 -2147483648(整数的最小值,处理了溢出情况)
    }
}

字符串全排列

题目 :输入一个字符串(含重复字符),打印所有字符排列(不重复输出)。
算法 :回溯法,递归交换字符位置生成排列,使用Set去重(或在递归时跳过重复字符)。

java 复制代码
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

// 该类用于生成字符串的所有不重复排列
public class StringPermutation {
    // 用于存储最终生成的所有排列结果
    private List<String> result = new ArrayList<>();
    // 存储输入字符串转换后的字符数组
    private char[] chars;

    // 生成字符串 s 的所有不重复排列
    public List<String> permutation(String s) {
        // 将输入的字符串转换为字符数组,方便后续操作
        chars = s.toCharArray();
        // 调用回溯函数,从索引 0 开始生成排列
        backtrack(0);
        // 返回存储所有排列结果的列表
        return result;
    }

    // 回溯函数,用于生成排列
    private void backtrack(int index) {
        // 当索引等于字符数组的长度时,说明已经生成了一个完整的排列
        if (index == chars.length) {
            // 将当前字符数组转换为字符串,并添加到结果列表中
            result.add(new String(chars));
            // 递归终止,返回上一层
            return;
        }
        // 使用 Set 记录已经交换过的字符,避免生成重复的排列
        Set<Character> used = new HashSet<>();
        // 从当前索引开始,遍历字符数组
        for (int i = index; i < chars.length; i++) {
            // 如果当前字符已经在 Set 中,说明已经交换过,跳过该字符
            if (used.contains(chars[i])) continue;
            // 将当前字符添加到 Set 中,表示已经使用过
            used.add(chars[i]);
            // 交换当前索引和 i 索引位置的字符
            swap(index, i);
            // 递归调用回溯函数,处理下一个索引位置
            backtrack(index + 1);
            // 回溯操作,恢复交换前的状态,以便尝试其他排列
            swap(index, i);
        }
    }

    // 交换字符数组中两个位置的字符
    private void swap(int i, int j) {
        // 临时存储 i 位置的字符
        char temp = chars[i];
        // 将 j 位置的字符赋值给 i 位置
        chars[i] = chars[j];
        // 将临时存储的字符赋值给 j 位置
        chars[j] = temp;
    }

    public static void main(String[] args) {
        // 创建 StringPermutation 类的实例
        StringPermutation solution = new StringPermutation();
        // 测试输入字符串 "abc",并打印生成的所有排列
        System.out.println(solution.permutation("abc")); 
        // 测试输入字符串 "aab",并打印生成的所有不重复排列
        System.out.println(solution.permutation("aab")); 
    }
}

复杂度分析

  • 时间复杂度:O (n×n!),n 为字符串长度。每个位置有 n, n-1, ..., 1 种可能,总共有 n! 种排列,每次生成排列需 O (n) 时间复制字符数组。
  • 空间复杂度:O (n),递归栈深度为 n,存储排列的空间为 n!×n(平均情况可视为 O (n!))。

找最小的 k 个数

题目 :从 n 个数中找到最小的 k 个数(k≤n)。
算法 :使用最大堆(优先队列)维护 k 个元素,堆顶是当前 k 个数中的最大值。遍历数组时,若当前数小于堆顶,则替换堆顶,保证堆中始终是最小的 k 个数。

java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;

// 该类用于找出数组中最小的 k 个数
public class SmallestKNumbers {
    // 该方法用于从数组 arr 中找出最小的 k 个数
    public List<Integer> getLeastNumbers(int[] arr, int k) {
        // 用于存储最终找到的最小的 k 个数
        List<Integer> result = new ArrayList<>();
        // 如果 k 为 0,说明不需要找最小的数,直接返回空列表
        if (k == 0) return result;

        // 使用优先队列来实现最大堆。
        // PriorityQueue 默认是最小堆,通过传入 (a, b) -> b - a 这个逆序比较器,将其转换为最大堆
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);

        // 遍历数组中的每个元素
        for (int num : arr) {
            // 如果最大堆中的元素数量小于 k,直接将当前元素添加到最大堆中
            if (maxHeap.size() < k) {
                maxHeap.offer(num);
            } else if (num < maxHeap.peek()) {
                // 如果最大堆已经有 k 个元素,且当前元素比堆顶元素(堆中的最大值)小
                // 则移除堆顶元素,将当前元素添加到堆中
                maxHeap.poll();
                maxHeap.offer(num);
            }
        }

        // 遍历最大堆,将堆中的元素依次添加到结果列表中
        while (!maxHeap.isEmpty()) {
            result.add(maxHeap.poll());
        }

        // 返回存储最小的 k 个数的列表
        return result;
    }

    public static void main(String[] args) {
        // 创建 SmallestKNumbers 类的实例
        SmallestKNumbers solution = new SmallestKNumbers();
        // 定义一个测试数组
        int[] arr = {3, 2, 1, 5, 6, 4};
        // 调用 getLeastNumbers 方法找出数组中最小的 2 个数,并打印结果
        System.out.println(solution.getLeastNumbers(arr, 2)); 
        // 结果可能是 [1, 2],由于堆结构不保证元素的顺序,实际输出顺序可能不同,可后续排序
    }
}

优化说明

  • 若需要结果排序,可在最后对列表排序:result.sort((a,b)->a-b);
  • 复杂度分析
    • 时间复杂度:O (n log k),每个元素入堆和出堆操作均为 O (log k),共 n 次操作。
    • 空间复杂度:O (k),堆中存储 k 个元素。

其他方法对比

  • 排序法:先排序数组,取前 k 个。时间复杂度 O (n log n),适用于 n 较小的情况。
  • 快速选择(类似快排分区):平均时间复杂度 O (n),但最坏情况 O (n²),且不适用于海量数据(需修改原数组)。

环形链表总集

java 复制代码
// 定义链表节点类,每个节点包含一个整数值和一个指向下一个节点的引用
class ListNode {
    int val;
    ListNode next;
    // 构造函数,用于初始化节点的值
    ListNode(int x) {
        val = x;
        next = null;
    }
}

public class CircularLinkedListAlgorithms {
    /**
     * 判断链表是否有环
     * @param head 链表的头节点
     * @return 如果链表有环返回 true,否则返回 false
     */
    public static boolean hasCycle(ListNode head) {
        // 若链表为空或者只有一个节点,不可能存在环,直接返回 false
        if (head == null || head.next == null) {
            return false;
        }
        // 慢指针,每次移动一步
        ListNode slow = head;
        // 快指针,每次移动两步
        ListNode fast = head;
        // 当快指针和其下一个节点都不为空时,继续循环
        while (fast != null && fast.next != null) {
            // 慢指针移动一步
            slow = slow.next;
            // 快指针移动两步
            fast = fast.next.next;
            // 如果慢指针和快指针相遇,说明链表存在环
            if (slow == fast) {
                return true;
            }
        }
        // 快指针到达链表末尾,说明链表不存在环
        return false;
    }

    /**
     * 找到环形链表的入环点
     * @param head 链表的头节点
     * @return 入环点的节点,如果链表无环则返回 null
     */
    public static ListNode detectCycle(ListNode head) {
        // 若链表为空或者只有一个节点,不可能存在环,直接返回 null
        if (head == null || head.next == null) {
            return null;
        }
        // 慢指针,每次移动一步
        ListNode slow = head;
        // 快指针,每次移动两步
        ListNode fast = head;
        // 标记链表是否有环
        boolean hasCycle = false;
        // 当快指针和其下一个节点都不为空时,继续循环
        while (fast != null && fast.next != null) {
            // 慢指针移动一步
            slow = slow.next;
            // 快指针移动两步
            fast = fast.next.next;
            // 如果慢指针和快指针相遇,说明链表存在环
            if (slow == fast) {
                hasCycle = true;
                break;
            }
        }
        // 如果链表无环,返回 null
        if (!hasCycle) {
            return null;
        }
        // 慢指针重新指向头节点
        slow = head;
        // 慢指针和快指针同时移动一步,直到它们相遇,相遇点即为入环点
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }

    /**
     * 计算环形链表的环长度
     * @param head 链表的头节点
     * @return 环的长度,如果链表无环则返回 0
     */
    public static int cycleLength(ListNode head) {
        // 若链表为空或者只有一个节点,不可能存在环,直接返回 0
        if (head == null || head.next == null) {
            return 0;
        }
        // 慢指针,每次移动一步
        ListNode slow = head;
        // 快指针,每次移动两步
        ListNode fast = head;
        // 标记链表是否有环
        boolean hasCycle = false;
        // 当快指针和其下一个节点都不为空时,继续循环
        while (fast != null && fast.next != null) {
            // 慢指针移动一步
            slow = slow.next;
            // 快指针移动两步
            fast = fast.next.next;
            // 如果慢指针和快指针相遇,说明链表存在环
            if (slow == fast) {
                hasCycle = true;
                break;
            }
        }
        // 如果链表无环,返回 0
        if (!hasCycle) {
            return 0;
        }
        // 初始化环的长度为 1
        int length = 1;
        // 快指针移动一步
        fast = fast.next;
        // 快指针继续移动,直到再次和慢指针相遇,记录移动的步数
        while (slow != fast) {
            fast = fast.next;
            length++;
        }
        return length;
    }

    public static void main(String[] args) {
        // 构建一个有环的链表示例
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        ListNode node4 = new ListNode(4);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node2; // 形成环

        // 调用 hasCycle 方法判断链表是否有环并输出结果
        System.out.println("链表是否有环: " + hasCycle(node1));
        // 调用 detectCycle 方法找到入环点
        ListNode entryPoint = detectCycle(node1);
        if (entryPoint != null) {
            // 若有入环点,输出入环点的值
            System.out.println("入环点的值: " + entryPoint.val);
        } else {
            // 若没有入环点,输出提示信息
            System.out.println("没有入环点");
        }
        // 调用 cycleLength 方法计算环的长度并输出结果
        System.out.println("环的长度: " + cycleLength(node1));
    }
}    

有序数组去重

java 复制代码
public class RemoveDuplicatesSortedArray {
    /**
     * 此方法用于移除有序数组中的重复元素,使每个元素只出现一次。
     * 并返回移除重复元素后数组的新长度。
     * 原数组会被修改,新长度之前的元素为去重后的元素。
     *
     * @param nums 输入的有序整数数组
     * @return 去重后数组的新长度
     */
    public static int removeDuplicates(int[] nums) {
        // 如果数组为空,直接返回 0
        if (nums == null || nums.length == 0) {
            return 0;
        }
        // 慢指针,指向去重后数组的最后一个位置
        int slow = 0;
        // 快指针,用于遍历数组
        for (int fast = 1; fast < nums.length; fast++) {
            // 如果快指针指向的元素和慢指针指向的元素不相等
            if (nums[fast] != nums[slow]) {
                // 慢指针向后移动一位
                slow++;
                // 将快指针指向的元素赋值给慢指针当前位置
                nums[slow] = nums[fast];
            }
        }
        // 慢指针的位置加 1 就是去重后数组的长度
        return slow + 1;
    }

    public static void main(String[] args) {
        // 定义一个有序数组
        int[] nums = {1, 1, 2, 2, 3, 4, 4, 4, 5};
        // 调用 removeDuplicates 方法进行去重
        int newLength = removeDuplicates(nums);
        System.out.println("去重后数组的新长度: " + newLength);
        System.out.print("去重后的数组元素: ");
        for (int i = 0; i < newLength; i++) {
            System.out.print(nums[i] + " ");
        }
    }
}    

数组合并区间

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class MergeIntervals {
    /**
     * 合并重叠的区间
     * @param intervals 输入的区间数组
     * @return 合并后不重叠的区间数组
     */
    public static int[][] merge(int[][] intervals) {
        // 如果输入数组为空或者长度为 0,直接返回空数组
        if (intervals == null || intervals.length == 0) {
            return new int[0][0];
        }
        // 按照区间的起始位置进行排序
        Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));

        // 用于存储合并后的区间
        List<int[]> merged = new ArrayList<>();
        // 取第一个区间作为初始的合并区间
        int[] current = intervals[0];
        // 遍历剩余的区间
        for (int i = 1; i < intervals.length; i++) {
            int[] interval = intervals[i];
            // 如果当前区间的结束位置大于等于下一个区间的起始位置,说明有重叠
            if (current[1] >= interval[0]) {
                // 更新当前区间的结束位置为两个区间结束位置的最大值
                current[1] = Math.max(current[1], interval[1]);
            } else {
                // 没有重叠,将当前区间加入到合并列表中
                merged.add(current);
                // 更新当前区间为下一个区间
                current = interval;
            }
        }
        // 将最后一个合并的区间加入到列表中
        merged.add(current);

        // 将列表转换为二维数组并返回
        return merged.toArray(new int[merged.size()][]);
    }

    public static void main(String[] args) {
        int[][] intervals = {{1, 3}, {2, 6}, {8, 10}, {15, 18}};
        int[][] mergedIntervals = merge(intervals);
        // 输出合并后的区间
        for (int[] interval : mergedIntervals) {
            System.out.println(Arrays.toString(interval));
        }
    }
}    
相关推荐
GISer_Jing2 小时前
前端算法实战:大小堆原理与应用详解(React中优先队列实现|求前K个最大数/高频元素)
前端·算法·react.js
小森77673 小时前
(三)机器学习---线性回归及其Python实现
人工智能·python·算法·机器学习·回归·线性回归
振鹏Dong3 小时前
超大规模数据场景(思路)——面试高频算法题目
算法·面试
uhakadotcom3 小时前
Python 与 ClickHouse Connect 集成:基础知识和实践
算法·面试·github
uhakadotcom3 小时前
Python 量化计算入门:基础库和实用案例
后端·算法·面试
写代码的小王吧3 小时前
【安全】Web渗透测试(全流程)_渗透测试学习流程图
linux·前端·网络·学习·安全·网络安全·ssh
uhakadotcom4 小时前
使用 Python 与 BigQuery 进行交互:基础知识与实践
算法·面试
uhakadotcom4 小时前
使用 Hadoop MapReduce 和 Bigtable 进行单词统计
算法·面试·github
XYY3694 小时前
前缀和 一维差分和二维差分 差分&差分矩阵
数据结构·c++·算法·前缀和·差分
longlong int4 小时前
【每日算法】Day 16-1:跳表(Skip List)——Redis有序集合的核心实现原理(C++手写实现)
数据库·c++·redis·算法·缓存