日常学习算法总结

本文深入探讨了数组和链表的基本数据结构,包括自定义数组和链表的实现,以及链表的反转、K个一组翻转和两两交换节点的算法。同时,讲解了如何获取数组中出现次数最多的元素,有效字母异位词的判断,以及有效括号的检查。
摘要由CSDN通过智能技术生成
一、基本数据结构之数组

自定义数组(类似ArrayList),数组必须存在在连续的地址空间,实现数组的增删操作。

public class CustomArray {
    private int[] array;
    // 元素个数
    private int size;
    private static final String TAG = "CustomArray";

    public CustomArray(int capacity) {
        array = new int[capacity];
        size = 0;
    }

    public void insert(int index, int element) {
        // 判断下标是否合法
        if (index < 0 || index > size) {
            throw new ArrayIndexOutOfBoundsException("数组越界,超出已有元素范围");
        }
        // 如果实际元素个数达到数组容量的上限,则进行扩容
        if (size >= array.length) {
            LogUtils.d(TAG, "我扩容了");
            expansionArray();
        }
        // 数组在内存中占有连续的内存空间,数组插入一个元素,插入位置后面的元素需要依次像后移动一位
        /*for (int i = size - 1; i >= index; i--) {
            array[i + 1] = array[i];
        }*/
        if (size - 1 - index >= 0) System.arraycopy(array, index, array, index + 1, size - 1 - index);
        array[index] = element;
        size++;
    }

    public int delete(int index) {
        if (index < 0 || index >= size) {
            throw new ArrayIndexOutOfBoundsException("数组越界,超出已有元素范围");
        }
        int delElement = array[index];
        // 需要将元素向前移动一位
        /*for (int i = index; i < size - 1; i++) {
            array[i] = array[i + 1];
        }*/
        // 下面这种方式其实是牺牲空间复杂度来提升时间复杂度
        if (size - 1 - index >= 0) System.arraycopy(array, index + 1, array, index, size - 1 - index);
        size--;
        return delElement;
    }

    public int get(int index) {
        if (index < 0 || index >= size) {
            throw new ArrayIndexOutOfBoundsException("数组越界,超出已有元素范围");
        }
        return array[index];
    }

    public void set(int index, int element) {
        if (index < 0 || index >= size) {
            throw new ArrayIndexOutOfBoundsException("数组越界,超出已有元素范围");
        }
        array[index] = element;
    }

    private void expansionArray() {
        // 进行两倍扩容
        int[] newArray = new int[array.length * 2];
        System.arraycopy(array, 0, newArray, 0, array.length);
        array = newArray;
    }

    public void outArray() {
        if (array == null) {
            return;
        }
        for (int element : array) {
            LogUtils.d(TAG, "数组元素:" + element);
        }
    }

    public int[] getArray() {
        return array;
    }

    public int getSize() {
        return size;
    }
}

二、基本数据结构之链表

自定义链表(类似LinkedList),实现增删操作

2.1、定义节点类

public class Node {
    public int data;
    public Node next;

    public Node() {
    }

    public Node(int data) {
        this.data = data;
    }
}

2.2、实现链表

public class CustomLinkedList {
    // 头结点指针
    private Node head;
    // 尾节点指针
    private Node last;
    // 链表长度
    private int size;

    public void insert(int index, int element) {
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException("链表越界,超出链表实际范围");
        }
        Node insertNode = new Node(element);
        if (size == 0) {
            // 链表长度为0,空链表
            head = insertNode;
            last = insertNode;
        } else if (index == 0) {
            // 插入头部
            insertNode.next = head;
            head = insertNode;
        } else if (index == size) {
            // 插入尾部
            last.next = insertNode;
            last = insertNode;
        } else {
            // 插入中间
            // 获取前一个节点
            Node preNode = get(index - 1);
            Node nextNode = preNode.next;
            preNode.next = insertNode;
            insertNode.next = nextNode;
        }
        size++;
    }

    public Node remove(int index) {
        if (index < 0 || index >= size) {
            throw new ArrayIndexOutOfBoundsException("链表越界,超出链表实际范围");
        }
        Node removeNode = null;
        if (index == 0) {
            // 删除头部
            removeNode = head;
            head = head.next;
        } else if (index == size - 1) {
            // 删除尾节点
            Node preNode = get(index - 1);
            removeNode = preNode.next;
            preNode.next = null;
            last = preNode;
        } else {
            // 删除中间节点
            Node preNode = get(index - 1);
            Node nextNode = preNode.next.next;
            removeNode = preNode.next;
            preNode.next = nextNode;
        }
        size--;
        return removeNode;
    }

    /**
     * 查询
     *
     * @param index
     * @return
     */
    public Node get(int index) {
        if (index < 0 || index >= size) {
            throw new ArrayIndexOutOfBoundsException("链表越界,超出链表实际范围");
        }
        Node tempNode = head;
        for (int i = 0; i < index; i++) {
            tempNode = tempNode.next;
        }
        return tempNode;
    }

    public String outPut() {
        StringBuilder builder = new StringBuilder();
        Node tempNode = head;
        while (tempNode != null) {
            builder.append(tempNode.data).append(" ");
            tempNode = tempNode.next;
        }
        return builder.toString();
    }

    public Node getNode() {
        return head;
    }

    public String outNode(Node node) {
        StringBuilder builder = new StringBuilder();
        Node tempNode = node;
        while (tempNode != null) {
            builder.append(tempNode.data).append(" ");
            tempNode = tempNode.next;
        }
        return builder.toString();
    }
}
三、反转链表

刚好利用上诉我们自定义的链表实现链表反转

/**
     * 反转链表
     * @param node
     * @return
     */
    public Node reverseLinkedList(Node node) {
        // 当前节点,将传递进来的头结点赋值给当前节点
        Node curNode = node;
        // 下一个节点
        Node nextNode;
        // 前一个节点
        Node preNode = null;
        while (curNode != null) {
            // 取出当前节点的下一个节点赋值给我们定义好的nextNode
            nextNode = curNode.next;
            // 将当前节点指向前一个节点实现反转
            curNode.next = preNode;
            // 将当前节点赋值给前一节点
            preNode = curNode;
            // 移动当前节点到下一个节点
            curNode = nextNode;
        }
        return preNode;
    }

调用:

val cusLinkedList = CustomLinkedList()
cusLinkedList.insert(0, 0)
cusLinkedList.insert(1, 1)
cusLinkedList.insert(2, 2)
cusLinkedList.insert(3, 3)
cusLinkedList.insert(4, 4)
cusLinkedList.insert(5, 5)
cusLinkedList.insert(6, 6)
cusLinkedList.insert(7, 7)
cusLinkedList.insert(8, 8)

val node = reverseLinkedList(cusLinkedList.node)
val nodeStr = cusLinkedList.outNode(node)
LogUtils.d(TAG, "反转链表: $nodeStr")

结果:

TestAlgorithmFragment==>: 反转链表: 8 7 6 5 4 3 2 1 0 
四、获取数组中出现次数最多的元素

4.1、双层for循环

/**
     * 获取数组中出现最多的元素和次数
     *
     * @param arr
     * @return
     */
    public int getArrayMostFrequent(int[] arr) {
        int mostElement = 0;
        int tempCount = 0;
        int timeCount = 0;
        for (int i = 0; i < arr.length; i++) {
            tempCount = 0;
            for (int j = 0; j < arr.length; j++) {
                if (arr[i] == arr[j]) {
                    tempCount++;
                }
            }
            if (tempCount > timeCount) {
                timeCount = tempCount;
                mostElement = arr[i];
            }
        }
        LogUtils.d(TAG, "出现次数最多的元素: " + mostElement + "==>次数: " + timeCount);
        return timeCount;
    }

4.2、利用map集合,将当前元素作为key,出现次数作为value,然后遍历map集合,value值最大的元素就是出现次数最多的元素

/**
     * 获取数组中出现最多的元素和次数
     * 使用map优化
     *
     * @param arr
     * @return
     */
    public int getArrayMostFrequentForMap(int[] arr) {
        Map<Integer, Integer> map = new HashMap<>();

        int mostElement = 0;
        int count = 0;
        int timeCount = 0;
        for (int i = 0; i < arr.length; i++) {
            if (map.containsKey(arr[i])) {
                map.put(arr[i], map.get(arr[i]) + 1);
            } else {
                map.put(arr[i], 1);
            }
        }
        for (Integer key : map.keySet()) {
            if (map.get(key) > timeCount) {
                timeCount = map.get(key);
                mostElement = key;
            }
            count++;
        }
        LogUtils.d(TAG + "使用Map优化", "出现次数最多的元素: " + mostElement + "==>最多元素次数: " + timeCount
                + "元素合并之后遍历次数: " + count);
        return timeCount;
    }
五、有效的字母异位词(leetcode第242题)

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

示例1:

输入: s = “anagram”, t = “nagaram”
输出: true

示例2:

输入: s = “rat”, t = “car”
输出: false

说明:
你可以假设字符串只包含小写字母。

思路:就是统计两个字母字符串中拥有的字母个数是否相同
1.可以利用两个长度都为 26 的字符数组来统计每个字符串中小写字母出现的次数,然后再对比是否相等;
2.可以只利用一个长度为 26 的字符数组,将出现在字符串 s 里的字符个数加 1,而出现在字符串 t 里的字符个数减 1,最后判断每个小写字母的个数是否都为 0。

解题:

private boolean isAnagram(String s, String t) {
        // 第一种方式
        /*if (s.length() != t.length()) {
            return false;
        }
        int[] arr1 = new int[26];
        int[] arr2 = new int[26];
        for (int i = 0; i < s.length(); i++) {
            arr1[s.charAt(i) - 'a']++;
            arr2[t.charAt(i) - 'a']++;
        }
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;*/

        // 第二种方式
        if (s.length() != t.length()) {
            return false;
        }
        int[] arr = new int[26];
        for (int i = 0; i < s.length(); i++) {
            arr[s.charAt(i) - 'a']++;
            arr[t.charAt(i) - 'a']--;
        }
        for (int count : arr) {
            if (count != 0) {
                return false;
            }
        }
        return true;
        /*if (s.length() != t.length()) {
            return false;
        }
        int[] arr = new int[26];
        for (int i = 0; i < s.length(); i++) {
            arr[s.charAt(i) - 'a']++;
        }
        for (int i = 0; i < t.length(); i++) {
            arr[t.charAt(i) - 'a']--;
            if (arr[t.charAt(i) - 'a'] < 0) {
                return false;
            }
        }
        return true;*/

        // 使用Java自带的高级函数
        /*if (s.length() != t.length()) {
            return false;
        }
        Map<Character, Integer> table = new HashMap<Character, Integer>();
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            table.put(ch, table.getOrDefault(ch, 0) + 1);
        }
        for (int i = 0; i < t.length(); i++) {
            char ch = t.charAt(i);
            table.put(ch, table.getOrDefault(ch, 0) - 1);
            if (table.get(ch) < 0) {
                return false;
            }
        }
        return true;*/
        /*if (s.length() != t.length()) {
            return false;
        }
        char[] str1 = s.toCharArray();
        char[] str2 = t.toCharArray();
        Arrays.sort(str1);
        Arrays.sort(str2);
        return Arrays.equals(str1, str2);*/
    }
六、K 个一组翻转链表(leetcode 25题)

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

示例:
给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5

说明:
你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

迭代法

/**
     * k个一组翻转链表
     * 1、迭代法
     * 2、递归
     * @param head
     * @param k
     * @return
     */
    public ListNode reverseKGroup(ListNode head, int k) {
        // 迭代
        // 构建一个边界节点
        ListNode virtualNode = new ListNode(0);
        virtualNode.next = head;
        // 头结点的前一个节点
        ListNode pre = virtualNode;
        while (head != null) {
            // 构建尾节点
            ListNode tail = pre;
            // 查看剩余部分长度是否大于等于 k
            for (int i = 0; i < k; i++) {
                tail = tail.next;
                if (tail == null) {
                    return virtualNode.next;
                }
            }
            // 下一个k组的头结点
            ListNode next = tail.next;
            // 翻转head -- tail之间的数据
            ListNode[] reverseArr = reverse(head, tail);
            // 重新给头、尾赋值,将翻转之前的尾赋值给头,反之亦然
            head = reverseArr[0];
            tail = reverseArr[1];
            // 把子链表重新接回原链表
            pre.next = head;
            tail.next = next;
            pre = tail;
            head = tail.next;
        }
        return virtualNode.next;
    }

    private ListNode[] reverse(ListNode head, ListNode tail) {
        ListNode pre = tail.next;
        ListNode cur = head;
        ListNode next;
        while (pre != tail) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return new ListNode[]{tail, head};
    }

迭代法

public ListNode reverseKGroup1(ListNode head, int k) {
        // 迭代
        // 构建一个边界节点
        ListNode virtualNode = new ListNode(0);
        virtualNode.next = head;
        // 头结点的前一个节点
        ListNode pre = virtualNode;
        // 构建尾节点
        ListNode tail = virtualNode;
        while (tail.next != null) {
            // 找到尾节点
            for (int i = 0; i < k && tail != null; i++) {
                tail = tail.next;
            }
            if (tail == null) {
                break;
            }
            // 每次循环都需要对当前节点和下一组的开始节点
            ListNode cur = pre.next;
            ListNode next = tail.next;
            tail.next = null;
            // 将翻转之后的子链表重新连接到总的链表中
            pre.next = reverse(cur);
            cur.next = next;
            // 移动pre节点和尾节点
            pre = cur;
            tail = pre;
        }

        return virtualNode.next;
    }

    /**
     * 翻转链表
     * @param head
     * @return
     */
    private ListNode reverse(ListNode head) {
        ListNode cur = head;
        ListNode pre = null;
        ListNode next;

        while (cur != null) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
七、两两交换链表中的节点(leetcode 24题)

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换

示例: 输入:head = [1,2,3,4]
输出:[2,1,4,3]

/**
     * 两两交换链表中的节点
     * 1、迭代法
     * 2、递归
     * @param head
     * @return
     */
    public ListNode swapPairs(ListNode head) {
        // 迭代
        /*ListNode virtualNode = new ListNode(0);
        virtualNode.next = head;
        ListNode pre = virtualNode;
        // 两两交换至少要有两个才能进入交换逻辑
        while (pre.next != null && pre.next.next != null) {
            // 赋值两个临时变量,代表交换的两个节点
            ListNode one = pre.next;
            ListNode two = pre.next.next;
            // 节点交换
            pre.next = two;
            one.next = two.next;
            two.next = one;
            pre = one;
        }
        return virtualNode.next;*/

        // 递归
        if (head == null || head.next == null) {
            return head;
        }
        ListNode next = head.next;
        head.next = swapPairs(next.next);
        next.next = head;
        return next;
    }
八、有效的括号(leetcode 20题)

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

示例 2:

输入:s = “()[]{}”
输出:true

代码编写::

public boolean isValid(String s) {
        char[] chars = s.toCharArray();
        if (chars.length == 0) return true;
        Map<Character, Character> map = new HashMap<>();
        map.put(')', '(');
        map.put(']', '[');
        map.put('}', '{');

        Stack<Character> stack = new Stack<>();
        for (char aChar : chars) {
            if (!isContains(aChar)) {
                return false;
            }
            if (aChar == '(' || aChar == '[' || aChar == '{') {
                stack.push(aChar);
            } else if (!stack.empty() && map.get(aChar) == stack.peek()) {
                stack.pop();
            } else {
                return false;
            }
        }
        return stack.empty();
    }

    private boolean isContains(char c) {
        List<Character> list = new ArrayList<>();
        list.add('(');
        list.add(')');
        list.add('[');
        list.add(']');
        list.add('{');
        list.add('}');
        return list.contains(c);
    }

简单优化之后

/**
     * 括号的有效性
     *
     * @param s
     * @return
     */
    public boolean isValid(String s) {
        char[] chars = s.toCharArray();
        if (chars.length == 0) return true;
        if (chars.length % 2 != 0) return false;
        /*Map<Character, Character> map = new HashMap<>();
        map.put(')', '(');
        map.put(']', '[');
        map.put('}', '{');*/

        Stack<Character> stack = new Stack<>();
        for (char aChar : chars) {
            if (!isContains(aChar)) {
                return false;
            }
            if (aChar == '(' || aChar == '[' || aChar == '{') {
                stack.push(aChar);
            } else if (!stack.empty() && getBracket(aChar) == stack.peek()
                /*map.get(aChar) == stack.peek()*/) {
                stack.pop();
            } else {
                return false;
            }
        }
        return stack.empty();
    }

    private char getBracket(char bracket) {
        if (bracket == ')') {
            return '(';
        } else if (bracket == ']') {
            return '[';
        } else if (bracket == '}') {
            return '{';
        } else {
            return ' ';
        }
    }

    private boolean isContains(char c) {
        return c == '(' || c == ')'
                || c == '[' || c == ']'
                || c == '{' || c == '}';
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃骨头不吐股骨头皮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值