漫画算法-小灰的算法之旅-面试中的算法(五)

本文基于《漫画算法 小灰的算法之旅》一书,详细讲解了链表有环判断、最小栈实现、最大公约数计算、判断2的整数次幂、无序数组排序最大相邻差等问题,涵盖了数据结构与算法的基础和进阶知识,包括链表、栈、动态规划、贪心算法等常见面试题目的解决方案。
摘要由CSDN通过智能技术生成

本文内容基于《漫画算法 小灰的算法之旅》,魏梦舒著。


1. 如何判断链表有环?

1.1 双重遍历

1.2 哈希表

1.3 追及问题

1.4 如果链表有环,如何求出环的长度?

1.5 如果链表有环,如何求出入环节点?

2. 最小栈的实现?

3. 如何求出最大公约数?

3.1 暴力枚举法

3.2 辗转相除法

3.3 更相减损术

3.4 基于移位的更相减损术

4. 如何判断一个数是否为2的整数次幂?

4.1 枚举

4.2 将乘替换乘移位

4.3 利用&

5. 无序数组排序后的最大相邻差?

5.1 排序遍历

5.2 利用计数排序的思想

5.3 利用桶排序的思想

6. 如何用栈实现队列?

7. 寻找全排列的下一个数?(字典序算法)

8. 删去k个数字后的最小值?(贪心算法)

8.1 常规思想解法

8.2 利用栈和外层循环内移提高效率

9. 如何实现大整数相加?

9.1 利用数组存储每一位

9.2 利用int的有效位数减少数组的长度

10. 如何求解金矿问题?(动态规划问题)

10.1 递归求解

10.2 自底向上求解

10.3 优化空间复杂度的自底向上求解

11. 寻找缺失的整数?

11.1 哈希表

11.2 排序

11.3 利用和作减法

11.4 问题扩展1

11.5 问题扩展2


1. 如何判断链表有环?

1.1 双重遍历

首先从头结点开始,依次遍历单链表中的每一个节点,每遍历一个新节点,就从头检查新节点之前的所有节点,如果发现新节点和之前的某个节点相同,则说明该节点被遍历过两次,链表有环。

public class IsCycle {

    private static class Node {
        int data;
        Node next;

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

    public static boolean isCycle(Node node) {
        Node node1 = node.next;
        int i = 1;
        while (node1 != null) {
            Node node2 = node;
            int j = 0;
            while (node2 != null && j < i) {
                if (node2 == node1) {
                    return true;
                }
                j++;
                node2 = node2.next;
            }
            i++;
            node1 = node1.next;
        }
        return false;
    }

    public static void main(String[] args) {
        Node node1 = new Node(5);
        Node node2 = new Node(3);
        node1.next = node2;
        Node node3 = new Node(7);
        node2.next = node3;
        Node node4 = new Node(2);
        node3.next = node4;
        Node node5 = new Node(6);
        node4.next = node5;
        Node node6 = new Node(8);
        node5.next = node6;
        Node node7 = new Node(1);
        node6.next = node7;
        node7.next = node4;

        System.out.println(isCycle(node1));
    }
}

假设链表的节点数量为n,则方法1的时间复杂度为:O(n^2),空间复杂度为O(1)。

1.2 哈希表

首先创建一个HashSet用来存储曾经遍历过的节点,每遍历一个节点,都用新节点同HashSet中存储的节点进行比较,如果相同,则说明链表有环。

public class IsCycle {

    private static class Node {
        int data;
        Node next;

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

    public static boolean isCycle1(Node node) {
        HashSet<Node> set = new HashSet<>();
        Node node1 = node;
        while (node1 != null) {
            if (set.contains(node1)) {
                return true;
            }
            set.add(node1);
            node1 = node1.next;
        }
        return false;
    }

    public static void main(String[] args) {
        Node node1 = new Node(5);
        Node node2 = new Node(3);
        node1.next = node2;
        Node node3 = new Node(7);
        node2.next = node3;
        Node node4 = new Node(2);
        node3.next = node4;
        Node node5 = new Node(6);
        node4.next = node5;
        Node node6 = new Node(8);
        node5.next = node6;
        Node node7 = new Node(1);
        node6.next = node7;
        node7.next = node4;

        System.out.println(isCycle1(node1));
    }
}

假设链表的节点数量为n,则方法1的时间复杂度为:O(n),空间复杂度为O(n)。

1.3 追及问题

首先创建两个对象引用,让它们同时指向这个链表的头节点,然后开始一个大循环,让引用1每次向后移动1个节点,引用2每次向后移动2个节点,然后比较两个引用指向的节点是否相同,如果相同,则说明链表有环。

类似于数学上的追及问题,因为是环形的,如果一个比另一个的速度快,那么它们肯定会相遇。

public class IsCycle {

    private static class Node {
        int data;
        Node next;

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

    public static boolean isCycle2(Node node) {
        Node node1 = node;
        Node node2 = node;
        while (node2 != null && node2.next != null) {
            node1 = node1.next;
            node2 = node2.next.next;
            if (node1 == node2) {
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Node node1 = new Node(5);
        Node node2 = new Node(3);
        node1.next = node2;
        Node node3 = new Node(7);
        node2.next = node3;
        Node node4 = new Node(2);
        node3.next = node4;
        Node node5 = new Node(6);
        node4.next = node5;
        Node node6 = new Node(8);
        node5.next = node6;
        Node node7 = new Node(1);
        node6.next = node7;
        node7.next = node4;

        System.out.println(isCycle2(node1));
    }
}

假设链表的节点数量为n,则方法1的时间复杂度为:O(n),空间复杂度为O(1)。

1.4 如果链表有环,如何求出环的长度?

当两个引用首次相遇,证明链表有环的时候,让两个引用从相遇点继续前进,并统计前进的次数,直到两个引用第二次相遇,此时这个前进次数就是环的长度。

public static int getCycleLength(Node node) {
    Node node1 = node;
    Node node2 = node;
    boolean firstMeet = false;
    int length = 0;
    while (node2 != null && node2.next != null) {
        node1 = node1.next;
        node2 = node2.next.next;
        if (node1 == node2) {
            if (!firstMeet) {
                //第一次相遇
                firstMeet = true;
            } else {
                //第二次相遇
                return length;
            }
        }
        if (firstMeet) {
            length++;
        }
    }
    return -1;
}

1.5 如果链表有环,如何求出入环节点?

只需要记住:从链表头节点到入环点的距离,等于从首次相遇点回到入环点的距离。

所以,当两个引用首次相遇,让一个引用回到头节点继续前进,一个节点从相遇点继续前进,都每次只前进一个节点,直到两个引用第二次相遇,第二次相遇点就是入环点。

public static Node getInCycleNode(Node node) {
    Node node1 = node;
    Node node2 = node;
    while (node2 != null && node2.next != null) {
        node1 = node1.next;
        node2 = node2.next.next;
        if (node1 == node2) {
            //第一次相遇
            node1 = node;
            break;
        }
    }

    while (node1 != null && node2 != null) {
        if (node1 == node2) {
            //第二次相遇
            return node1;
        }
        node1 = node1.next;
        node2 = node2.next;
    }
    return null;
}

2. 最小栈的实现?

【问题】实现一个栈,该栈有出栈、入栈、取最小元素3个方法,要保证这3个方法的时间复杂度都是O(1)。

【思路】用两个栈来实现,栈A存所有元素,栈B存栈A当前的最小元素。

public class MinStack {
    private static Stack<Integer> mainStack = new Stack<>();
    private static Stack<Integer> minStack = new Stack<>();

    public static void push(int element) {
        mainStack.push(element);
        if (minStack.isEmpty() || minStack.peek() >= element) {
            minStack.push(element);
        }
    }

    public static int pop() {
        if (mainStack.peek().intValue() == minStack.peek()) {
            minStack.pop();
        }
        return mainStack.pop();
    }

    public static int getMin() {
        return minStack.peek();
    }

    public static void main(String[] args) {
        MinStack.push(4);
        MinStack.push(9);
        MinStack.push(7);
        MinStack.push(3);
        MinStack.push(8);
        MinStack.push(5);
        System.out.println(MinStack.getMin());
        MinStack.pop();
        MinStack.pop();
        MinStack.pop();
        System.out.println(MinStack.getMin());
    }
}

3. 如何求出最大公约数?

3.1 暴力枚举法

public class GreatestCommonDivisor {

    public static int getGreatestCommonDivisor(int a, int b) {
        int big = a > b ? a : b;
        int small = a < b ? a : b;
        if (big % small == 0) {
            return small;
        }

        for (int i = small / 2; i > 1; i--) {
            if (small % i == 0 && big % i == 0) {
                return i;
            }
        }
        return 1;
    }

    public static void main(String[] args) {
        System.out.println(getGreatestCommonDivisor(25, 5));
        System.out.println(getGreatestCommonDivisor(100, 80));
        System.out.println(getGreatestCommonDivisor(27, 14));
    }
}

该方法的问题是:如果传入的整数是10000和10001,就需要循环10000 / 2 - 1次,效率比较低。时间复杂度为O(min(a, b))。

3.2 辗转相除法

两个正整数a和b(a > b),它们的最大公约数等于a % b和b的最大公约数。

public class GreatestCommonDivisor {

    public static int getGreatestCommonDivisor1(int a, int b) {
        int big = a > b ? a : b;
        int small = a < b ? a : b;
        if (big % small == 0) {
            return small;
        }

        return getGreatestCommonDivisor1(big % small, small);
    }

    public static void main(String[] args) {
        System.out.println(getGreatestCommonDivisor1(25, 5));
        System.out.println(getGreatestCommonDivisor1(100, 80));
        System.out.println(getGreatestCommonDivisor1(27, 14));
    }
}

该方法的问题是:当两个整数比较大时,%的效率比较低。时间复杂度可以近似为O(log(max(a, b)))。

3.3 更相减损术

两个正整数a和b(a > b),它们的最大公约数等于a - b和b的最大公约数。

public class GreatestCommonDivisor {

    public static int getGreatestCommonDivisor2(int a, int b) {
        if (a == b) {
            return a;
        }
        int big = a > b ? a : b;
        int small &#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值