算法实战-OJ之旅

算法虽然不是特别简单,但没有你想象中的那么难。

Sort Array By Parity easy

AC-17ms.
按照《算法导论》排序一章的一些概念,第二种可以称为是原址的(in-place)。
First Try:
用了另一个数组来保存结果,如果是奇数进行swap操作,同时在else中还要维护偶数的位置正确性。

class Solution {
    public int[] sortArrayByParity(int[] A) {
        int[] result = new int[A.length];
        for (int i = 0,tmp = 0; i < A.length; i++) {
            if (A[i] % 2 == 1) {
                result[A.length - tmp - 1] = A[i];
                tmp ++;
            } else {
                result[i - tmp] = A[i];
            }
        }
        return result;
    }
}

提交后瞄了眼,发现可以只操作原数组,改了一下,这次只需要将even数据移到前面即可。

        for (int i = 0,evenIndex = 0; i < A.length; i++) {
            if (A[i] % 2 == 0) {
                int tmp = A[i];
                A[i] = A[evenIndex];
                A[evenIndex] = tmp;
                evenIndex ++;
            }
        }
        System.err.println(System.nanoTime() - start + "ms.");
        return A;

To Lower Case easy

作了个弊,逃
str.toLowerCase();
论思路的话,拿到str的char数组,判断是否位于大写字母的ASCII码范围内,如果是,原有ASCII码+32存入,否则原样存入,利用String(char[])构造方法返回小写。大概类似这位老哥的解法戳一下
A的ASCII码是65,a是97

剑指Offer

二维数组中的查找

思路: 从右上角(或左上角)开始进行判断。
其他:每一行进行二分查找,虽然也可以实现,但是复杂度为O(NlgN),不推荐

public boolean Find(int target, int[][] array) {
        if (array == null || array.length == 0 || array[0].length == 0 ) {
            return false;
        }
        int rows = array.length;//代表行数
        int cols = array[0].length ;//代表列数
        if (rows > 0 && cols > 0) {
            int row = 0;
            int column = cols - 1;
            while (row < rows && column >= 0) {
                if (array[row][column] == target) {
                    return true;
                } else if (array[row][column] > target) {
                    column--;
                } else {
                    ++row;
                }
            }
        }
        return false;
    }

替换空格

这里有个有意思的知识点,空格ASCII码是32,16进制表示为0x20,因此替换时表示为%20;#ASCII码是35,16进制0x23,因此替换时表现为%23;
如果可以新开辟字符串来完成替换,那就比较简单了,现在考虑的是在原有字符串上进行替换

思路:空格替换为‘%20’,原来一个字符,现在就多了2个.注意:setLength的过程其实是找了另一个数组,将原有数据拷贝上去了,数据组成是原有数据+新扩容的。然后才是移动。这样而跟创建一个新数组,然后直接赋值相比,反而有多次一举之嫌疑。这一点,等什么时候再学习一下C/C++时再来对比。用Java里的append和replace有作弊的嫌疑,因此这里仍旧按照书上思想来实现。不过略有改动,while改成了for循环,更简洁也更容易理解一些。

public String replaceSpace(StringBuffer str) {
        int oldLength = str.length();
        if (oldLength == 0) {
            return str.toString();
        }
        int spaceNum = 0;
        for (int i = 0; i < oldLength; i++) {
            if (str.charAt(i) == ' ') spaceNum++;
        }
        // 计算出替换后的字符串长度
        int newLength = oldLength + spaceNum * 2;
        str.setLength(newLength);
        for (int i = oldLength - 1; i >= 0; i--) {
            if (str.charAt(i) != ' ') {
                str.setCharAt(--newLength, str.charAt(i));
            } else {
                str.setCharAt(--newLength, '0');
                str.setCharAt(--newLength, '2');
                str.setCharAt(--newLength, '%');
            }
        }
        return str.toString();
    }

从尾到头打印链表

注意这里是单链表,只有next指针。又学到一点,因为是单链表,所以只能从头到尾遍历,但是打印又是要从尾到头打印,因此就是后进入的先出来,符合LIFO栈的数据结构定义,因此可以使用Stack来实现。(惭愧,Stack还是第一次用呢。另外再说一点,linkedlist也可以当成栈来使用的)

public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        Stack<Integer> stack = new Stack<>();
        while (listNode != null) {
            stack.push(listNode.val);
            listNode = listNode.next;
        }
        ArrayList result = new ArrayList();
        while (!stack.isEmpty()) {
            result.add(stack.pop());
        }
        return result;
    }
// 顺便把从头到尾打印也给写了一下
public ArrayList<Integer> printListFromHeadToTail(ListNode listNode) {
        ArrayList<Integer> result = new ArrayList<>();
        while (listNode != null) {
            result.add(listNode.val);
            listNode = listNode.next;
        }
        return result;
    }

这里顺便还知道了,递归在本质上就是一个栈结构。因此还有递归写法如下:

    ArrayList result  = new ArrayList();
    public ArrayList<Integer> printListFromTailToHeadRecursive(ListNode listNode) {

        if (listNode != null) {
            if (listNode.next != null) {
                printListFromTailToHeadRecursive(listNode.next);
            }
            result.add(listNode.val);
        }
        return result;
    }

顺便把简单的链表结构也贴上;当有了对应结构的时候,此时就可以把对应的《算导》里面的事项转换成具体的代码实现了。

public class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }

    }

重建二叉树

这里又有个知识点,宽度优先遍历,就是一层一层的来,从左到右遍历输出。
此问题有一定难度,对应回答里有一个高票答案,看起来比较简洁,但是易读性较差。不过,经过对比,递归调用时的参数是一致的。
这里稍作解释,constructCore里的参数,pre和in数组是不变的,不讲;startPreorder是指左孩子前序序列下标起点,endPreorder是左孩子前序序列结束下标;另两个是中序序列的对应参数。
另外,构建右子树时,if条件表达式,有些不是很清楚为何,留作思考;再者,对于另一个最优解答,找到rootIndex之后,并不判断是否需要构建左子树而是直接构建,猜测应该是没进for循环,构造了一个null吧。这两个问题暂且留作思考。

public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
        if (pre == null || in == null) {
            return null;
        }
        return constructCore(pre, 0, pre.length - 1, in, 0, in.length - 1);
    }

    private TreeNode constructCore(int[] pre, int startPreorder, int endPreOrder, int[] in, int startInorder, int endInorder) {
        if (startPreorder > endPreOrder || startInorder > endInorder)
            return null;
        // 前序遍历的第一个是root值
        int rootValue = pre[startPreorder];
        TreeNode root = new TreeNode(rootValue);
        int rootInorder = startInorder;

        // 找出内部节点在中序序列中的位置
        for (int i = startInorder; i <= endInorder; i++) {
            if (in[i] == rootValue) {
                rootInorder = i;
                break;
            }
        }
        // 左子树的长度
        int leftLength = rootInorder - startInorder;
        // 前序序列截止下标
        int leftPreorderEnd = startPreorder + leftLength;
        if (leftLength > 0) {
            // 构建左子树
            root.left = constructCore(pre, startPreorder + 1, leftPreorderEnd,
                    in, startInorder, rootInorder - 1);
        }
        if (leftLength < endPreOrder - startPreorder) {
            // 构建右子树
            root.right = constructCore(pre, leftPreorderEnd + 1, endPreOrder,
                    in, rootInorder + 1, endInorder);

        }

        return root;
    }

位运算

五种:与、或、异或、左移、右移。
异或:同0异1
左移:左移n位,右边补n个0
右移:数字是无符号数值,用0填充;有符号数值,用符号位填充。(提到有符号数,这里会涉及到反码、补码,《计算机科学导论》里面有讲,只有哪天再复习一下了,只看不记实在是太容易忘记了,哈哈,不过再提一下,个人觉得这本书翻译的有挺多地方都有问题,还是要辩证看待。比如:P33 二进制补码表示法范围分为两半:0000-0110以及0111-1111,应该是0000-0111,1000-1111)
有符号数:0表示正整数,1表示负整数
反码:无论正负,简单反转各个位即可。参照维基百科反码解释,还有一些条件:正数反码等于原码,负数反码等于符号位不变,反转其余位。反码里有两个0,+0:0000,-0:1111

补码:首先,从右边复制位,直到有1被复制;接着,反转其余的位。(或是负数除符号位外取反+1)
一些补充:
以二进制补码格式存储整数,计算机遵循以下步骤:
将整数变成n位的二进制数;
如果整数是正整数或零,以其原样存储;如果是负数,计算机取其补码存储。(所以,以这样的性质为准,则书上举的例子实在是太差劲了!)


从二进制补码格式还原整数:
如果左位是1,取其补码,左位是0,不进行操作。
二次补码,二次反码运算均得到原数。
补码其实是取反之后+1的结果。
查看更多:关于二进制补码、反码的一些解释
这里再提一点位运算:以4位为例

  1. 左移:
    1000,是负数,以补码形式存在,如何计算其十进制是多少呢?
    补码取反,0111,表示的十进制数7,+1得到8,加上负号-8.
    而-1 <<2, -1是int,4字节32位,二进制表示为32个1,左移2位之后变为30个1,2个0;取反变成30个0,2个1,十进制就是3,+1得到4,加负号为-4.
  2. 右移
    正数补0,负数补1
    -1 >> 1 = -1
    3.无符号右移
    无论该数是正负,均补0
    -1 >>> 1 = 2147483647 即2^31 - 1,Integer.MAX_VALUE.
    解释一下,32个1无符号右移之后变成0跟31个1.即2^0 + 2^1 + 2^2 + ...+2^30 = 2 ^31 - 1;(等比数列求和)
    再来一个位运算交换位置:

    贴一下博主地址
    另外关于位运算再贴一篇文章详解Java里的位运算

二进制中的1的个数

思路:用1和n做与运算,如果不是0,则说明n对应位置是1
这里if条件用!=0还要更好一些

    public int NumberOf1(int n) {
        int flag = 1, count = 0;
        while (flag != 0) {
            if ((n & flag) == flag ) {
                count ++;
            }
            flag <<= 1;
        }
        return count;
    }

然而这种解法的话至少要循环32次(int 4字节32位),如果有几个1就循环几次就好了

一个整数n做运算n & (n - 1)可以把n最右侧的1变成0

    public int NumberOf1(int n) {
        int count = 0;
        while (n != 0) {
            count++;
            n &= (n - 1);
        }
        return count;
    }

用两个栈实现队列

第一次就AC,哇哇哇。

思路:用stack1来存放push进来的值,每次出栈的时候,只要Stack2非空,则先出栈Stack2的数;一旦stack2空了,就立马取stack1里面已经存在的数,压入stack2中,再出栈。这里没处理为空时pop()的边界问题,因为没有时,pop()也该报错,EmptyStackException,Stack类也是这么处理的。顺便再说下pop()和peek()了,pop()弹出并返回栈顶的值,peek()返回栈顶的值。

Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();

    public void push(int node) {
        stack1.push(node);
    }

    public int pop() {
        if (!stack2.isEmpty()) return stack2.pop();
        while (!stack1.isEmpty()) {
            stack2.push(stack1.pop());
        }
        return stack2.pop();
    }

顺便再看看两个队列实现一个栈怎么实现:

思路:push时两个队列哪个有数据,就往哪个push;都没有数据,随便一个push。
pop时,队列1有n条数据,就把前n-1条数据放入队列2,队列2有数据就放到队列1中去。

    ArrayDeque<Integer> queue1 = new ArrayDeque<>();
    ArrayDeque<Integer> queue2 = new ArrayDeque<>();

    public void stackPush(int node) {
        if (queue1.isEmpty() & queue2.isEmpty()) {
            queue1.add(node);
            return;
        }
        if (queue1.isEmpty()) {
            queue2.add(node);
        } else {
            queue1.add(node);
        }
    }

    public int stackPop() {
        if (queue1.isEmpty() && queue2.isEmpty()) {
            return -1;
        }
        if (queue1.isEmpty()) { // 如果队列2非空,

            while (queue2.size() > 1) {
                queue1.add(queue2.poll());
            }
            return queue2.poll();
        } else { // 队列1非空

            while (queue1.size() > 1) {
                queue2.add(queue1.poll());
            }
            return queue1.poll();
        }

    }

员工年龄排序

参见这篇文章

下标对应年龄,值对应该年龄出现了几次;最后一个二重循环将年龄重新放入ages数组。看似O(n^2),实际上,这个二重循环只执行了ages.length次。

public void sortAge(int[] ages){
        int oldAge = 70;
        int youngAge = 17;
        int[] timeOfAge = new int[oldAge+1];

        for(int i = youngAge; i <= oldAge; i++){
            timeOfAge[i] = 0;
        }

        for(int j = 0; j < ages.length; j++){
            int a = ages[j];
            timeOfAge[a]++;
        }

        int index = 0;
        for(int i = youngAge; i <= oldAge; i++){
            for(int j = 0; j < timeOfAge[i]; j++){
                ages[index] = i;
                index++;
            }
        }
    }

旋转数组中的最小数

这道题就有点奇怪了,按照书上思路,自己测试一些,也是可以正常输出的,但是不知为何,无法通过OJ

public int minNumberInRotateArray(int[] array) {
        if (array.length < 0) return -1;
        int low = 0, high = array.length - 1;
        int mid = low;// 解决原本就是已排序数组的问题
        while (array[low] >= array[high]) {
            if (array[low] == array[high] && array[mid] == array[high]) {
                return minInOrder(array, low, high);
            }
            if (low - high == 1) {
                mid = high;
                break;
            }
            mid = (low + high) / 2;
            if (array[mid] >= array[low]) {
                low = mid;
            } else {
                high = mid;
            }
        }
        return array[mid];
    }

    private int minInOrder(int[] array, int low, int high) {
        int result = array[low];
        for (int i = low + 1; i < high; i++) {
            if (result > array[i]) {
                result = array[i];
            }
        }
        return result;
    }

数值的整数次方

这道题简直太有趣了,将指数分为奇偶,基数就是1半平方再乘base;偶数不用乘base。注意:0^0在数学上是没意义的,这里简单的处理为1.指数为负即为求正数的倒数。
public double Power(double base, int exponent) { if (exponent == 0) return 1; if (exponent == 1) return base; if (exponent < 0) return 1/Power(base, -exponent); double result = Power(base, exponent >> 1); result *= result ; if ((exponent & 1) == 1) { result *= base; } return result; }
在O(1)时间删除链表节点
思路:1.遍历找到待删除节点i的前驱,修改前驱next指向

  1. 不需要遍历,将i的后继值放入i中,将i指向i的next的next。不过注意一些边界情况:(1)删除的节点不是tail节点(2)删除的链表只有一个节点(3)链表有多个节点,删除最后一个节点(4)删除的节点是否在链表中

    将奇数移到前面偶数移到后面

    先给出不保证顺序的版本(即只要奇数在前就行),这一点同LeetCode那题基本类似。

public void reOrderArray(int [] array) {
        if (array == null || array.length == 0) return;
        int begin = 0;
        int end = array.length - 1;
        while (begin < end) {
            // 找到偶数
            while (begin < end && (array[begin] & 1) != 0) {
                begin++;
            }

            // 找到奇数
            while (begin < end && (array[end] & 1) == 0) {
                end--;
            }

            if (begin < end) {
                int tmp = array[begin];
                array[begin] = array[end];
                array[end] = tmp;
            }
        }

    }

再给出有保证顺序版本(这个就需要顺次移动了):

if (array == null || array.length == 0) return;
        int begin = 0;
        int end ;
        while (begin < array.length) {
            // 找到偶数
            while (begin < array.length && (array[begin] & 1) != 0) {
                begin++;
            }

            end = begin + 1;

            // 找到奇数
            while (end < array.length && (array[end] & 1) == 0) {
                end++;
            }

            if (end < array.length) {
                int tmp = array[end];
                // 这里只能采取从后往前的方式
                for (int i = end - 1; i >= begin; i--) {
                    array[i+1] = array[i];
                }
                array[begin++] = tmp;
                System.err.println(Arrays.toString(array));
            } else {
                break;
            }
        }

话说鲁棒性,这个光从汉语意义上讲实在是让人费解的很啊╮(╯▽╰)╭

链表中倒数第k个结点

思路: 两个指针,一个先跑k-1次,另一个再开始跑

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        // 代表走到第几个节点
        int count = 0;
        ListNode first = head, second = head ;
        while (first != null) {
            first = first.next;
            count ++;
            // 当第一个链表走到k- 1时,第二个链表也跑起来
            if (count > k ) {
                second = second.next;
            }
        }
        // 如果链表的数量还不足k个
        if (count < k) return null;
        return second;
    }
}

原文地址:https://www.cnblogs.com/studentytj/p/10428614.html

时间: 2024-10-20 22:30:11

算法实战-OJ之旅的相关文章

算法实战:根据Key或Value对Map进行排序及其应用

摘要: 我们知道,Map是 Java Collection Framework 的重要成员,也是我们最常用的容器类之一.Map的实现多种多样,包括HashMap.LinkedHashMap等.但是,无论实际中使用哪种实现,我们在编程过程中常常会遇到诸如根据Key或Value对Map进行排序.保持Map插入顺序等问题,本文特别针对以上几个问题给出了具体解法,并分享华为一道与我们主题极为相关的笔试题. 版权声明: 本文原创作者:书呆子Rico 作者博客地址:http://blog.csdn.net/

Rabin Karp 算法实战

关键字 Rabin karp 算法, C++, ubuntu 14.04, linux, big integer, gmp 为了计算冗余度, 我写出了如下算法 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

BAT大牛亲授-个性化推荐算法实战

第1章 个性化推荐算法综述 个性化推荐算法综述部分,主要介绍个性化推荐算法综述,本课程内容大纲以及本课程所需要准备的编程环境与基础知识. 1-1 个性化推荐算法综述 1-2 个性化召回算法综述 第2章 基于邻域的个性化召回算法LFM 本章节重点介绍一种基于邻域的个性化召回算法,LFM.从LFM算法的理论知识与数学原理进行介绍.并结合公开数据集,代码实战LFM算法. 2-1 LFM算法综述 2-2 LFM算法的理论基础与公式推导 2-3 基础工具函数的代码书写 2-4 LFM算法训练数据抽取 2-

【算法学习笔记】33.在线算法 SJTU OJ 1006 求和游戏

1006. 求和游戏 Description 石柱上有一排石头键盘,每个键上有一个整数.请你在键盘上选择两个键,使这两个键及其之间的键上的数字和最大.如果这个最大的和不为正,则输出“Game Over". Input Format 第1行:键的个数n. 第2..n+1行:键上的数字整数 ai. −100≤ai≤100 对于70%的数据,2≤n≤1,000 对于100%的数据,2≤n≤1,000,000 Output Format 一行,最大和或者”Game Over". Sample

Swift 算法实战之路:基本语法与技巧

Swift是苹果新推出的编程语言,也是苹果首个开源语言.相比于原来的Objective-C,Swift要更轻便和灵活.笔者最近使用Swift实践了大量的算法(绝大部分是硅谷各大公司的面试题),将心得体会总结于下.此文并不是纯粹讨论Swift如何实现某一个具体的算法或者数据结构,如冒泡排序.深度优先遍历,或是树和栈,而是总结归纳一些Swift常用的语法和技巧,以便大家在解决面试题中使用. 基本语法 先来看下面一段代码 func swap(chars:[Character], p: Int, q:

android 中文件加密 解密 算法实战

现在项目里面有一个需求,本项目里面下载的视频和文档都不允许通过其他的播放器播放,在培训机构里面这样的需求很多.防止有人交一份钱,把所有的课件就拷给了别人.这样的事情培训机构肯定是不愿意的.现在我项目里面也出了这么个需求.下面介绍一下我的实现. 思路: 首先下载文件,这个就不说了,java代码写个下载管理器. 下载完成后存储文件的时候不是直接存储,要加密存储,加密方法是将文件的每个字节与这个字节在流中的下标做异或运算. 在我们项目里面播放的时候要解密,方法也是将文件的每个字节与这个字节在流中的下标

《算法实战策略》-chaper19-队列、栈和双端队列

对于计算机专业的学生来说,他们一定会很熟悉一句话:程序设计 = 算法 + 数据结构.而根据笔者的理解,所谓程序设计其实就是为了编程解决实际问题,所谓算法是一种解决问题某种思维的方法,但是思维需要得到编程实践,这就需要基于数据结构.一个好的数据结构能够让我们更快更高效得处理数据,有些模拟性.数学背景并不深厚的的问题,仅仅基于高效的数据结构就可以解决.那么这一章节,我们就单独将队列.栈.双端队列拿出来,结合具体的题目,看看它们是如何灵活的运用到解题策略当中的. 考虑到笔者在<入门经典>和<啊

八大排序算法实战:思想与实现

摘要: 所谓排序,就是根据排序码的递增或者递减顺序把数据元素依次排列起来,使一组任意排列的元素变为一组按其排序码线性有序的元素.本文将介绍八种最为经典常用的内部排序算法的基本思想与实现,包括插入排序(直接插入排序,希尔排序).选择排序(直接选择排序,堆排序).交换排序(冒泡排序,快速排序).归并排序.分配排序(基数排序),并给出各种算法的时间复杂度.空间复杂度和稳定性. 版权声明: 本文原创作者:书呆子Rico  作者博客地址:http://blog.csdn.net/justloveyou_/

匈牙利算法实战codevs1022覆盖

1022 覆盖 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 大师 Master 题解 查看运行结果 题目描述 Description 有一个N×M的单位方格中,其中有些方格是水塘,其他方格是陆地.如果要用1×2的矩阵区覆盖(覆盖过程不容许有任何部分重叠)这个陆地,那么最多可以覆盖多少陆地面积. 输入描述 Input Description 输入文件的第一行是两个整数N,M  (1<=N,M<=100),第二行为一个整数K( K<=50),接下来的K行,每行两个整数X