算法题

一、链表

1、反转一个单链表(206)

public static ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode node = null;
while (head != null) {
ListNode temp = head;
head = head.next;
temp.next = node;
node = temp;
}
return node;
}

2、给定一个链表,判断链表中是否有环(第二种好一些,141)

    public static boolean hasCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        while (head != null) {
            if (set.contains(head)) {
                return true;
            } else {
                set.add(head);
            }
            head = head.next;
        }
        return false;
    }
    public static boolean hasCycle1(ListNode head) {
        if (head == null) {
            return false;
        }
        ListNode fast = head.next;
        ListNode slow = head;
        while (fast != null && slow != null && fast.next != null) {
            if (fast == slow) {
                return true;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        return false;
    }

3、将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的(21)

public static ListNode mergeTwoLists1(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        ListNode head = null;
        ListNode pre = new ListNode();
        while (l1 != null || l2 != null) {
            ListNode currentNode = new ListNode();
            if (l1 == null) {
                pre.next = l2;
                break;
            }
            if (l2 == null) {
                pre.next = l1;
                break;
            }
            if (l1.val < l2.val) {
                currentNode.val = l1.val;
                l1 = l1.next;
            } else {
                currentNode.val = l2.val;
                l2 = l2.next;
            }
            if (head == null) {
                pre = currentNode;
                head = pre;

            } else {
                pre.next = currentNode;
                pre = pre.next;
            }
        }
        return head;
    }
public static ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }

        if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }

4、给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点(19)

public static ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode sentry = new ListNode(-1);
        sentry.next = head;
        int length = 0;
        ListNode node = sentry;
        while (node != null) {
            ++length;
            node = node.next;
        }
        int x = 0;
        ListNode cNode = sentry;
        while (cNode != null) {
            if (n == (length - x - 1)) {
                cNode.next = cNode.next.next;
                return sentry.next;
            }
            ++x;
            cNode = cNode.next;
        }
        return head;
    }
public static ListNode removeNthFromEnd1(ListNode head, int n) {
        ListNode sentry = new ListNode(-1);
        sentry.next = head;
        ListNode pre = sentry;
        ListNode second = sentry;
        while (n >= 0) {
            second = second.next;
            --n;
        }

        while (second != null) {
            pre = pre.next;
            second = second.next;
        }

        pre.next = pre.next.next;
        return sentry.next;
    }

5、给定一个带有头结点 head 的非空单链表,返回链表的中间结点(876)

 public static ListNode middleNode(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

二、排序

冒泡,插入,选择排序实现代码都非常简单,对于小规模数据的排序,用起来非常高效。但是在大规模数据排序的时候,这个时间复杂度还是稍微有点高,所以更倾向于使用时间复杂度为 O(nlogn) 的排序算法。

1、冒泡

/**
     * 冒泡排序
     * 稳定排序、原地排序,时间复杂度,最好是o(n),最坏是o(n的平方),平均是o(n的平方)
     *
     * @param nums
     */
    public static void bubbleSort(int[] nums) {
        int length = nums.length;
        if (length <= 1) {
            return;
        }
        for (int i = 0; i < length; i++) {
            boolean flag = true;
            for (int j = 1; j < length - i; j++) {
                if (nums[j] < nums[j - 1]) {
                    int temp = nums[j];
                    nums[j] = nums[j - 1];
                    nums[j - 1] = temp;
                    flag = false;

                }
            }
            if (flag) {
                return;
            }
        }

    }

2、插入排序

/**
     * 插入排序
     * 稳定排序、原地排序,时间复杂度,最好是o(n),最坏是o(n的平方),平均是o(n的平方)
     * 冒泡和插入排序一般选择插入排序 原因:冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要 3 个赋值操作,而插入排序只需要 1 个
     *
     * @param nums
     */
    public static void insertSort(int[] nums) {
        int length = nums.length;
        if (length <= 1) {
            return;
        }

        for (int i = 1; i < length; i++) {
            int j = i;
            int value = nums[j];
            for (; j > 0; j--) {
                if (nums[j - 1] > value) {
                    nums[j] = nums[j - 1];
                } else {
                    break;
                }
            }
            nums[j] = value;
        }
    }

 3、选择排序

/**
     * 选择排序
     * 不稳定排序、原地排序,时间复杂度,最好是o(n的平方),最坏是o(n的平方),平均是o(n的平方)
     */
    public static void selectionSort(int[] nums) {
        int n = nums.length;
        if (n <= 0) {
            return;
        }
        for (int i = 0; i < n - 1; i++) {
            int value = nums[i];
            int index = i;
            for (int j = i; j < n - 1; j++) {
                if (nums[j + 1] < nums[i]) {
                    nums[i] = nums[j + 1];
                    index = j + 1;
                }
            }
            nums[index] = value;
        }
    }

4、快速排序

 /**
     * 快速排序
     * 不稳定算法,是原地排序
     *
     * @param nums
     */
    public static void quictSort(int[] nums) {
        if (nums.length <= 1) {
            return;
        }
        quictSortC(nums, 0, nums.length - 1);
    }

    public static void quictSortC(int[] nums, int left, int right) {
        if (left >= right) {
            return;
        }

        int q = partition(nums, left, right);
        quictSortC(nums, left, q - 1);
        quictSortC(nums, q + 1, right);
    }
public static int partition(int[] nums, int left, int right) {
int value = nums[left];
int i = left;
int j = right;
while (i < j) {
while (i < j && nums[j] >= value) {
j--;
}
if (i < j) {
nums[i] = nums[j];
}
while (i < j && nums[i] < value) {
i++;
}
if (i < j) {
nums[j] = nums[i];
}
}
nums[i] = value;
return i;

}
 

5、归并排序

/**
     * 归并排序(分治思想))
     * 最好、最坏、平均时间复杂度都是o(nlogn),不如快排常用的原因是不是原地排序算法
     * 归并排序是稳定算法、不是原地排序,空间复杂度为o(n),因为每个子方法执行完空间就被回收了
     *
     * @param nums
     */
    public static void mergeSort(int[] nums) {
        if (nums.length <= 1) {
            return;
        }
        mergeSortC(nums, 0, nums.length - 1);
    }

    public static void mergeSortC(int[] nums, int min, int max) {
        if (min >= max) {
            return;
        }
        int p = (min + max) / 2;
        mergeSortC(nums, min, p);
        mergeSortC(nums, p + 1, max);
        merge(nums, min, p, max);
    }

    public static void merge(int[] data, int left, int center, int right) {
        // 临时数组
        int[] tmpArr = new int[data.length];
        // 右数组第一个元素索引
        int mid = center + 1;
        // third 记录临时数组的索引
        int third = left;
        // 缓存左数组第一个元素的索引
        int tmp = left;
        while (left <= center && mid <= right) {
            // 从两个数组中取出最小的放入临时数组
            if (data[left] <= data[mid]) {
                tmpArr[third++] = data[left++];
            } else {
                tmpArr[third++] = data[mid++];
            }
        }
        // 剩余部分依次放入临时数组(实际上两个while只会执行其中一个)
        while (mid <= right) {
            tmpArr[third++] = data[mid++];
        }
        while (left <= center) {
            tmpArr[third++] = data[left++];
        }
        // 将临时数组中的内容拷贝回原数组中
        // (原left-right范围的内容被复制回原数组)
        while (tmp <= right) {
            data[tmp] = tmpArr[tmp++];
        }
    }

6、利用快排查找第k大元素

//searchK为入口,时间复杂度O(n) 

public static Integer searchK(Integer[] nums, int k){ if(nums.length ==0 || nums.length < k){ return null; } return searchC(nums, 0, nums.length-1, k); } private static Integer searchC(Integer[] arr, int l, int r, int n ){ int base = partition(l,r,arr); if(base == n-1){ //找到第n大元素直接返回 return arr[n-1]; }else if(base<n-1){ //在右半部分查找 return searchC(arr, base+1, r,n); }else{ //在左半部分查找 return searchC(arr, l, base-1,n); } } public static int partition(int s, int e, Integer[] nums) { int x = nums[s]; while (s < e) { while (s < e && nums[e] > x) { --e; } if (s < e) { nums[s] = nums[e]; } while (s < e && nums[s] <= x) { ++s; } if (s < e) { nums[e] = nums[s]; } } nums[s] = x; return s; }

 7、堆排序

public static void swap(int[] nums, int i, int max){
        int temp = nums[i];
        nums[i] = nums[max];
        nums[max] = temp;
    }

    //堆化
    public static void heapify(int[] nums,int n, int i){
        while(true){
            int maxIndex = i;

            int leftIndex = (i<<1)+1;
            if(leftIndex < n && nums[leftIndex] > nums[i]){
                maxIndex = leftIndex;
            }
            int rightIndex = (i+1)<<1;
            if(rightIndex < n && nums[rightIndex] > nums[maxIndex]){
                maxIndex = rightIndex;
            }

            if(maxIndex == i){
                break;
            }
            swap(nums, maxIndex, i);
            i = maxIndex;
        }
    }

    /**
     * 堆排序
     * 1、对倒数第一个非叶子节点开始调整数组,使之成为一个大顶堆
     * 2、堆顶和最后一个交换,对堆顶进行堆化,使0到n-1的数复合大顶堆
     * 3、不断重复2过程
     * 
     * 整个堆排序的过程,都只需要极个别临时存储空间,所以堆排序是原地排序算法。堆排序包括建堆和排序两个操作,建堆过程的时间复杂度是 O(n),排序过程的时间复杂度是 O(nlogn),所以,堆排序整体的时间复杂度是 O(nlogn)。堆排序不是稳定的排序算法
     */
    public static void heapSort(int[] nums) {
        if (nums.length <= 1) {
            return;
        }

        //建大顶堆
        for (int i = nums.length / 2-1; i >= 0; --i) {
            heapify(nums, nums.length, i);
        }

        //排序
        for(int i = nums.length-1;i>0;--i){
            swap(nums, i, 0);
            heapify(nums, i, 0);
        }

    }

 

三、二分查找

1、查找等于k的在数组中的下标

/**
     * 二分查找
     * 局限性:
     * 1、二分查找依赖的是顺序表结构,简单点说就是数组
     * 2、二分查找针对的是有序数据,二分查找只能用在插入、删除操作不频繁,一次排序多次查找的场景,不然每次插入要维护有序性
     * 3、数据量太小不适合二分查找,顺序遍历就好,不过数据之间的比较操作非常耗时,可用二分查找
     * 4、数据量太大也不适合二分查找,因为数组要求连续空间,对内存要求高
     * 二分查找虽然性能比较优秀,但应用场景也比较有限。底层必须依赖数组,并且还要求数据是有序的。对于较小规模的数据查找,我们直接使用顺序遍历就可以了,二分查找的优势并不明显。二分查找更适合处理静态数据,也就是没有频繁的数据插入、删除操作
     * 时间复杂度 O(logn)
     *
     * @param nums
     * @param value
     */
    public static int bsearch(int[] nums, int value) {
        if (nums.length == 0) {
            return -1;
        }
        int min = 0;
        int max = nums.length - 1;

        while (max >= min) {
            int mid = (min + ((max - min) >> 1));
            if (value > nums[mid]) {
                min = mid + 1;
            } else if (value < nums[mid]) {
                max = mid - 1;
            } else {
                return mid;
            }
        }

        return -1;
    }

    /**
     * 二分查找递归
     *
     * @param nums
     * @param left
     * @param rigth
     * @param value
     * @return
     */
    public static int bsearch(int[] nums, int left, int rigth, int value) {
        if (left > rigth) {
            return -1;
        }

        int mid = left + ((rigth - left) >> 1);
        if (nums[mid] == value) {
            return mid;
        } else if (value > nums[mid]) {
            return bsearch(nums, mid + 1, rigth, value);
        } else {
            return bsearch(nums, left, mid - 1, value);
        }
    }

 2、查找有序数组中第一个等于k的下标

public static int bsearch(int[] nums, int k) {
        if (nums.length == 0) {
            return -1;
        }
        int min = 0;
        int max = nums.length - 1;
        while (min <= max) {
            int mid = min + ((max - min) >> 1);
            if (nums[mid] < k) {
                min = mid + 1;
            } else {
                max = mid - 1;
            }
        }
        if (nums[min] == k) {
            return min;
        }
        return -1;
    }

//方法2
public int bsearch(int[] a, int n, int value) {
    int low = 0;
    int high = n - 1;
    while (low <= high) {
        int mid = low + ((high - low) >> 1);
        if (a[mid] > value) {
            high = mid - 1;
        } else if (a[mid] < value) {
            low = mid + 1;
        } else {
            if ((mid == 0) || (a[mid - 1] != value)) return mid;
            else high = mid - 1;
        }
    }
    return -1;
}

3、查找有序数组中最后一个等于k的下标

 public int bsearch(int[] a, int n, int value) {
        int low = 0;
        int high = n - 1;
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (a[mid] > value) {
                high = mid - 1;
            } else if (a[mid] < value) {
                low = mid + 1;
            } else {
                if ((mid == n - 1) || (a[mid + 1] != value)) return mid;
                else low = mid + 1;
            }
        }
        return -1;
    }

4、查找第一个大于等于给定值的元素

public int bsearch(int[] a, int n, int value) {
        int low = 0;
        int high = n - 1;
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (a[mid] >= value) {
                if ((mid == 0) || (a[mid - 1] < value)) return mid;
                else high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return -1;
    }

5、查找最后一个小于等于给定值的元素

public int bsearch7(int[] a, int n, int value) {
        int low = 0;
        int high = n - 1;
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (a[mid] > value) {
                high = mid - 1;
            } else {
                if ((mid == n - 1) || (a[mid + 1] > value)) return mid;
                else low = mid + 1;
            }
        }
        return -1;
    }

 四、二叉树

public class Node {
    private Object data;    //节点数据
    private Node leftChild; //左子节点的引用
    private Node rightChild; //右子节点的引用
    //打印节点内容
    public void display(){
        System.out.println(data);
    }
 
}
//中序遍历
public void infixOrder(Node current){
    if(current != null){
        infixOrder(current.leftChild);
        System.out.print(current.data+" ");
        infixOrder(current.rightChild);
    }
}
 
//前序遍历
public void preOrder(Node current){
    if(current != null){
        System.out.print(current.data+" ");
        preOrder(current.leftChild);
        preOrder(current.rightChild);
    }
}
 
//后序遍历
public void postOrder(Node current){
    if(current != null){
        postOrder(current.leftChild);
        postOrder(current.rightChild);
        System.out.print(current.data+" ");
    }
}


 

//前序遍历非递归的方式
    public void preOrder(Node root) {
        Stack<Node> stack = new Stack<Node>();
        while (true) {
            while (root != null) {
                System.out.print(root.value + "\t");
                stack.push(root);
                root = root.left;
            }
            if (stack.isEmpty()) {
                break;
            }
            root = stack.pop();
            root = root.right;
        }
    }

    //中序遍历采用非递归的方式
    public void inOrder(Node root) {
        Stack<Node> stack = new Stack<>();
        while (true) {
            while (root != null) {
                stack.push(root);
                root = root.left;
            }
            if (stack.isEmpty()) {
                break;
            }
            root = stack.pop();
            System.out.print(root.value + "\t");
            root = root.right;
        }
    }

    //非递归后续遍历
    public void postOrder(Node node) {
        Stack<Node> stack = new Stack<>();
        Node cur, pre = null;       //pre为额外结点,记录前一个结点,用于打印
        stack.push(node);
        while (!stack.isEmpty()) {
            cur = stack.peek();
            if ((cur.left == null && cur.right == null)
                    || (pre != null && (cur.left == pre || cur.right == pre))) {            //判断是否为上述条件决定是否打印
                Node temp = stack.pop();
                System.out.print(temp.value + " ");
                pre = temp;
            } else {
                if (cur.right != null) {        //右结点入栈
                    stack.push(cur.right);
                }

                if (cur.left != null) {
                    stack.push(cur.left);
                }
            }
        }
    }

 五、八皇后问题(回溯算法)

int[] result = new int[8];//全局或成员变量,下标表示行,值表示queen存储在哪一列
    public void cal8queens(int row) { // 调用方式:cal8queens(0);
        if (row == 8) { // 8个棋子都放置好了,打印结果
            printQueens(result);
            return; // 8行棋子都放好了,已经没法再往下递归了,所以就return
        }
        for (int column = 0; column < 8; ++column) { // 每一行都有8中放法
            if (isOk(row, column)) { // 有些放法不满足要求
                result[row] = column; // 第row行的棋子放到了column列
                cal8queens(row+1); // 考察下一行
            }
        }
    }

    private boolean isOk(int row, int column) {//判断row行column列放置是否合适
        int leftup = column - 1, rightup = column + 1;
        for (int i = row-1; i >= 0; --i) { // 逐行往上考察每一行
            if (result[i] == column) return false; // 第i行的column列有棋子吗?
            if (leftup >= 0) { // 考察左上对角线:第i行leftup列有棋子吗?
                if (result[i] == leftup) return false;
            }
            if (rightup < 8) { // 考察右上对角线:第i行rightup列有棋子吗?
                if (result[i] == rightup) return false;
            }
            --leftup; ++rightup;
        }
        return true;
    }

    private void printQueens(int[] result) { // 打印出一个二维矩阵
        for (int row = 0; row < 8; ++row) {
            for (int column = 0; column < 8; ++column) {
                if (result[row] == column) System.out.print("Q ");
                else System.out.print("* ");
            }
            System.out.println();
        }
        System.out.println();
    }

六、 0-1背包问题

题目:我们现在期望选择几件物品,装载到背包中。在不超过背包所能装载重量的前提下,如何让背包中物品的总重量最大?

1、回溯算法解法

private static int total = 0; //背包可以装的最大重量

    /**
     * 回溯算法,其实就是穷举,列出来所有可能的结果,找出最优的解,时间复杂度是指数级别的 O(2^n),n为物品的数量
     * 背包,总的承载重量是limitNum,现在有nums.length个物品,每个物品的重量不等,并且不可分割。
     *
     * @param limitNum   背包限制的重量
     * @param currentNum 表示当前已经装进去的物品的重量和
     * @param nums       表示每个物品的重量
     * @param n          表示考察到哪个物品了
     */
    public static void packageMax(int limitNum, int currentNum, int[] nums, int n) {
        if (currentNum == limitNum || n == nums.length) { //currentNum == limitNum 表示装满了, n == nums.length 表示所有物品都考察完了
            if (currentNum > total) {
                total = currentNum;
            }
            return;
        }
        //不装物品进背包
        packageMax(limitNum, currentNum, nums, n + 1);
        //装物品进背包
        if ((nums[n] + currentNum) <= limitNum) { // 已经超过可以背包承受的重量的时候,就不要再装了
            packageMax(limitNum, nums[n] + currentNum, nums, n + 1);
        }
    }

    public static void main(String[] args) {
        int[] nums = new int[]{8, 14, 50, 40, 80, 4, 15};
        int k  = knapsack(nums, 100, nums.length);
        System.out.println(k);
    }

2、回溯算法使用备忘录方式优化解法

static int limitNum = 100; //背包限制的重量
    static int[] nums = new int[]{8, 14, 50, 40, 80, 4, 15}; //表示每个物品的重量
    static boolean[][] status = new boolean[nums.length][limitNum + 1]; //计算过的物品备忘录
    private static int total = 0; //背包可以装的最大重量

    /**
     * 回溯算法,其实就是穷举,列出来所有可能的结果,找出最优的解,时间复杂度是指数级别的 O(2^n),n为物品的数量
     * 使用备忘录方式优化,记录已经算过的状态,再遇到直接从算过的取,然后不重复计算
     * 背包,总的承载重量是limitNum,现在有nums.length个物品,每个物品的重量不等,并且不可分割。
     * 我们现在期望选择几件物品,装载到背包中。在不超过背包所能装载重量的前提下,如何让背包中物品的总重量最大?
     *
     * @param currentNum 表示当前已经装进去的物品的重量和
     * @param n          表示考察到哪个物品了
     */
    public static void packageMax1(int currentNum, int n) {
        if (currentNum == limitNum || n == nums.length) { //currentNum == limitNum 表示装满了, n == nums.length 表示所有物品都考察完了
            if (currentNum > total) {
                total = currentNum;
            }
            return;
        }
        if (status[n][currentNum]) {
            return;
        }
        status[n][currentNum] = true;
        //不装物品进背包
        packageMax1(currentNum, n + 1);
        //装物品进背包
        if ((nums[n] + currentNum) <= limitNum) { // 已经超过可以背包承受的重量的时候,就不要再装了
            packageMax1(nums[n] + currentNum, n + 1);
        }
    }

    public static void main(String[] args) {
        packageMax1(0,0);
        System.out.println(total);
    }

3、动态规划解法

public static void main(String[] args) {
        int k = knapsack(nums, 100, nums.length);
        System.out.println(k);
    }

    /**
     * 动态规划,把求解分为n个阶段,每个阶段选择把物品放进背包和不放进背包,会有多个状态,合并每一阶段重复的状态,根据上一个阶段求出下一个阶段的值
     * 最后一个阶段接近物品重量的值就是要求解的值
     * 时间复杂度是 O(n*w),空间复杂度也是 O(n*w),动态规划是一种空间换时间的解决思路
     * @param nums     表示每个物品的重量
     * @param limitNum 背包限制的重量
     * @param n        物品的数量
     * @return
     */
    public static int knapsack(int[] nums, int limitNum, int n) {
        boolean[][] status = new boolean[n][limitNum + 1];
        status[0][0] = true;//第一行数据特殊出来,哨兵优化
        if (nums[0] <= limitNum) {
            status[0][nums[0]] = true;
        }
        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= limitNum; j++) {
                if (status[i - 1][j]) {
                    status[i][j] = status[i - 1][j]; //与 status[i][j] = true 等价, 不放第i个物品进背包的状态
                }
            }
            for (int j = 0; j <= limitNum - nums[i]; ++j) {//把第i个物品放入背包
                if (status[i - 1][j] == true) {
                    status[i][j + nums[i]] = true;
                }
            }
        }

        for (int i = limitNum; i >= 0; --i) {
            if (status[n - 1][i]) {
                return i;
            }
        }
        return 0;
    }

 

/**
     * 针对上一个使用二维数组动态规划的优化
     */
    public static int knapsack1(int[] nums, int limitNum, int n) {
        boolean[] status = new boolean[limitNum + 1];
        status[0] = true;//第一行数据特殊出来,哨兵优化
        if (nums[0] <= limitNum) {
            status[nums[0]] = true;
        }
        for (int i = 1; i < n; i++) {
            for (int j = limitNum - nums[i]; j >= 0; j--) {////把第i个物品放入背包
                if (status[j] == true) {
                    status[j + nums[i]] = true;
                }
            }
        }

        for (int i = limitNum; i >= 0; --i) {
            if (status[i]) {
                return i;
            }
        }
        return 0;
    }

七、0-1背包问题升级

问题描述:对于一组不同重量、不同价值、不可分割的物品,我们选择将某些物品装入背包,在满足背包最大重量限制的前提下,背包中可装入物品的总价值最大是多少呢?

1、回溯算法实现

static int limitNum = 100; //背包限制的重量
    static int[] nums = new int[]{8, 14, 50, 40, 80, 4, 15}; //表示每个物品的重量
    static int[] values = new int[]{2, 5, 3, 7, 15, 18, 9}; //表示每个物品的价值
    private static int total = 0; //背包可以装的重量的最大价值

    /**
     * 回溯算法,其实就是穷举,列出来所有可能的结果,找出最优的解,时间复杂度是指数级别的 O(2^n),n为物品的数量
     * 使用备忘录方式优化,记录已经算过的状态,再遇到直接从算过的取,然后不重复计算
     * 背包,总的承载重量是limitNum,现在有nums.length个物品,每个物品的重量不等,并且不可分割。
     *
     * @param currentNum 表示当前已经装进去的物品的重量和
     * @param value      表示当前已经装进去的物品的重量的最大价值
     * @param n          表示考察到哪个物品了
     */
    public static void packageMax1(int currentNum, int n, int value) {
        if (currentNum == limitNum || n == nums.length) { //currentNum == limitNum 表示装满了, n == nums.length 表示所有物品都考察完了
            if (value > total) {
                total = value;
            }
            return;
        }

        //不装物品进背包
        packageMax1(currentNum, n + 1, value);
        //装物品进背包
        if ((nums[n] + currentNum) <= limitNum) { // 已经超过可以背包承受的重量的时候,就不要再装了
            packageMax1(nums[n] + currentNum, n + 1, values[n] + value);
        }
    }

    public static void main(String[] args) {
        packageMax1(0, 0, 0);
        System.out.println(total);
    }

2、动态规划实现

public static void main(String[] args) {
        int[] nums = new int[]{8, 14, 50, 40, 80, 4, 15}; //表示每个物品的重量
        int[] values = new int[]{2, 5, 3, 7, 15, 18, 9}; //表示每个物品的价值
        int v = knapsack(nums, values, 100, nums.length);
        System.out.println(v);
    }

    /**
     * 动态规划,把求解分为n个阶段,每个阶段选择把物品放进背包和不放进背包,会有多个状态,合并每一阶段重复的状态(相同状态选择价值大的),根据上一个阶段求出下一个阶段的值
     * 最后一个阶段接近物品重量的值就是要求解的值
     *
     * @param nums     表示每个物品的重量
     * @param values   表示每个物品的价值
     * @param limitNum 背包限制的重量
     * @param n        物品的数量
     * @return
     */
    public static int knapsack(int[] nums, int[] values, int limitNum, int n) {
        int[][] status = new int[n][limitNum + 1];
        for (int i = 0; i < n; ++i) { // 初始化states
            for (int j = 0; j < limitNum + 1; ++j) {
                status[i][j] = -1;
            }
        }
        status[0][0] = 0;
        if (nums[0] <= limitNum) {
            status[0][nums[0]] = values[0];
        }
        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= limitNum; j++) {
                if (status[i - 1][j] >= 0) {
                    status[i][j] = status[i - 1][j]; // 不放第i个物品进背包的状态
                }
            }
            for (int j = 0; j <= limitNum - nums[i]; ++j) {//把第i个物品放入背包
                if (status[i - 1][j] >= 0) {
                    int v = status[i - 1][j] + values[i];
                    if (v > status[i][j + nums[i]]) {
                        status[i][j + nums[i]] = v;
                    }

                }
            }
        }

        int maxValue = -1;
        for (int i = 0; i <= limitNum; i++) {
            if (status[n - 1][i] > maxValue) {
                maxValue = status[n - 1][i];
            }
        }
        return maxValue;
    }

 八、购物车问题

淘宝的“双十一”购物节有各种促销活动,比如“满 200 元减 50 元”。假设你女朋友的购物车中有 n 个(n>100)想买的商品,她希望从里面选几个,在凑够满减条件的前提下,让选出来的商品价格总和最大程度地接近满减条件(200 元),这样就可以极大限度地“薅羊毛”

/**
     * 动态规划
     * 假设你女朋友的购物车中有 n 个(n>100)想买的商品,她希望从里面选几个,在凑够满减条件的前提下,让选出来的商品价格总和最大程度地接近满减条件(200 元),这样就可以极大限度地“薅羊毛”
     * 要找到凑单大于200的最小值的所有商品,这个可凑到的额度选为200*3,如果超过这个金额没有凑单的必要
     *
     * @param nums     表示每个商品的价格
     * @param limitNum 要凑单的满减额度
     * @param n        物品的数量
     * @return
     */
    public static void order(int[] nums, int limitNum, int n) {
        boolean[][] status = new boolean[n][3 * limitNum + 1];
        status[0][0] = true;//第一行数据特殊出来,哨兵优化
        if (nums[0] <= 3 * limitNum) {
            status[0][nums[0]] = true;
        }
        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= 3 * limitNum; j++) {
                if (status[i - 1][j]) {
                    status[i][j] = status[i - 1][j]; //与 status[i][j] = true 等价, 不放第i个物品进背包的状态
                }
            }
            for (int j = 0; j <= 3 * limitNum - nums[i]; ++j) {//把第i个物品放入背包
                if (status[i - 1][j] == true) {
                    status[i][j + nums[i]] = true;
                }
            }
        }

        int i;//凑单最小的金额
        for (i = limitNum; i <= 3 * limitNum; i++) {
            if (status[n - 1][i]) {
                break;
            }
        }
        if (i == 3 * limitNum + 1) {//没有凑到
            return;
        }
        for (int j = n - 1; j >= 1; j--) {
            if ((i - nums[j]) >= 0 && status[j - 1][i - nums[j]]) {
                System.out.println(nums[j]); //购买了这个商品
                i = i - nums[j];
            }

        }
        if (i != 0) {
            System.out.println(nums[0]);
        }
    }

 九、矩阵问题

题目描述:有一个 n 乘以 n 的矩阵 w[n][n]。矩阵存储的都是正整数。棋子起始位置在左上角,终止位置在右下角。我们将棋子从左上角移动到右下角。每次只能向右或者向下移动一位。从左上角到右下角,会有很多不同的路径可以走。我们把每条路径经过的数字加起来看作路径的长度。那从左上角移动到右下角的最短路径长度是多少呢?

1、回溯算法实现

  static int minDist = Integer.MAX_VALUE;//最短距离

    /**
     * 回溯算法
     * @param i  矩阵行
     * @param j  矩阵列
     * @param dist 计算出来的路径
     * @param nums 矩阵
     * @param n 矩阵的边界
     */
    public static void minDistBT(int i, int j, int dist, int[][] nums, int n) {
        dist += nums[i][j];
        if (i == n-1 && j == n-1) {
            if (dist < minDist) {
                minDist = dist;
            }
            return;
        }
        if (j < n-1) { //往下走
            minDistBT(i, j + 1, dist, nums, n);
        }

        if (i < n-1) { //往右走
            minDistBT(i + 1, j, dist, nums, n);
        }
    }

    public static void main(String[] args) {
        int[][] nums = new int[][]{{1, 3, 5, 9}, {2, 1, 3, 4}, {5, 2, 6, 7}, {6, 8, 4, 3}};
        minDistBT(0, 0, 0, nums, 4);
        System.out.println(minDist);
    }

1、动态规划--状态转移法

public static void main(String[] args) {
        int[][] nums = new int[][]{{1, 3, 5, 9}, {2, 1, 3, 4}, {5, 2, 6, 7}, {6, 8, 4, 3}};
        System.out.println(minDistDP(nums, nums.length));
    }


    /**
     * 动态规划--状态转移法
     * @param matrix
     * @param n
     * @return
     */
    public static int minDistDP(int[][] matrix, int n) {
        int[][] status = new int[n][n];

        status[0][0] = matrix[0][0];
        for (int i = 1; i < n; i++) { //初始化第一行
            status[0][i] = status[0][i - 1] + matrix[0][i];
        }

        for (int i = 1; i < n; i++) { //初始化第一列
            status[i][0] = status[i - 1][0] + matrix[i][0];
        }

        for (int i = 1; i < n; i++) { //计算每一个矩阵的值
            for (int j = 1; j < n; j++) {
                int min = status[i - 1][j] > status[i][j - 1] ? status[i][j - 1] : status[i - 1][j];
                status[i][j] = matrix[i][j] + min;
            }
        }
        return status[n - 1][n - 1];
    }

 

2、动态规划--状态转移方程法

    private static int[][] nums = {{1, 3, 5, 9}, {2, 1, 3, 4}, {5, 2, 6, 7}, {6, 8, 4, 3}}; //矩阵
    private static int n = nums.length; //n*n矩阵
    private static int[][] mem = new int[n][n]; //推导出来的值

    public static int minDistDP1(int i, int j) {
        if (i == 0 && j == 0) {
            return nums[i][j];
        }

        if (mem[i][j] > 0) {
            return mem[i][j];
        }
        int up = Integer.MAX_VALUE;
        if (i > 0) {
            up = minDistDP1(i - 1, j);
        }

        int left = Integer.MAX_VALUE;
        if (j > 0) {
            left = minDistDP1(i, j - 1);
        }

        int min = up > left ? left : up;
        mem[i][j] = nums[i][j] + min;
        return mem[i][j];
    }

    public static void main(String[] args) {
        System.out.println(minDistDP1(n - 1, n - 1));
    }

 

posted @ 2020-07-07 21:25  ALittleYuan  阅读(26)  评论(0)    收藏  举报