近期学习及准备
算法题
long溢出
做网易笔试题,c(99,100)按照如下办法溢出:-90929067940350457
BigInteger 比long更大的整数
public static long paizu(int n,int m,long k ) {
if(n-1 ==0||m==0)
return 1;
long t =1;
long sum = n+m-1;
// m = Math.min(n-1, m);//C(m+n) n=C(m+n) m 取最小即可
for(int i=0;i<m;i++) {
t *= sum-i;
t /= (i+1);
// if(t>k)
// break;
}
return t;
}
}
可以优化:C(m+n) n=C(m+n) m 取最小即可 减少循环次数
public static long paizu(int n,int m,long k ) {
if(n-1 ==0||m==0)
return 1;
long t =1;
long sum = n+m-1;
m = Math.min(n-1, m);//C(m+n) n=C(m+n) m 取最小即可
for(int i=0;i<m;i++) {
t *= sum-i;
t /= (i+1);
if(t>k)
break;
}
return t;
}
}
java四舍五入并保留m位小数
//例子是输出六位小数的百分比,不用输出百分号
int wei =1000000;
double suoqiu = (double)((int)(((double)(k-1)/(double)n)*100*wei+0.5))/wei;
System.out.println(String.format("%.6f", suoqiu));
//如果是正常的小数
int wei =1000000;
double suoqiu = (double)((int)(((double)(k-1)/(double)n)*wei+0.5))/wei;
System.out.println(String.format("%.6f", suoqiu));//6是输出的小数的位数
合并两个有序数组
import java.util.Arrays;
public class mergeSorted {
public static int[] merge(int[] nums1, int m, int[] nums2, int n) {
int i=m-1;
int j =n-1;
int k = m+n-1;
while(i>=0&&j>=0) {
if(nums1[i]>=nums2[j]) {
nums1[k] = nums1[i];
i--;
}else {
nums1[k] = nums2[j];
j--;
}
k--;
}
while(j>=0) {
nums1[k--]=nums2[j--];
}
return nums1;
}
public static void main(String[] args) {
int[] nums1 = new int[] {1,2,3,0,0,0};
int[] nums2= {2,5,6};
int[] a = merge(nums1, 3,nums2, 3);
//System.out.println(a.toString());//错误:打印的是地址值
System.out.println(Arrays.toString(a));
}
}
值得注意:1.打印出数组的值:
//System.out.println(a.toString());//错误:打印的是地址值
System.out.println(Arrays.toString(a));
2.用的是nums1保留原有合并结果:
循环结束后,有可能i和j还大于等于0,若j大于等于0,那么需要继续循环,将nums2中的数字继续拷入nums1。若是i大于等于0,那么就不用管,因为混合数组本身就房子nums1中。
如果用nums3的话,循环结束i/j 有大于等于0 的都需要循环
DP动态规划
硬币1:
322.零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
import java.util.Arrays;
public class Coin1 {
public static int coinChange(int[] coins, int amount) {
if(coins.length==0)
return -1;
int[] dp = new int[amount+1];
Arrays.fill(dp,1,dp.length,Integer.MAX_VALUE);
for(int i=0;i<coins.length;i++) {
for(int j=coins[i];j<=amount;j++) {
if(dp[j-coins[i]] != Integer.MAX_VALUE) {
dp[j] = Math.min(dp[j], dp[j-coins[i]]+1);
}
}
}
if(dp[amount] != Integer.MAX_VALUE)
return dp[amount];
return -1;
}
public static void main(String[] args) {
int[] coins1 = {1, 2, 5};
int amount1=11;
System.out.println(coinChange(coins1,amount1));
int[] coins2 = {2};
int amount2=3;
System.out.println(coinChange(coins2,amount2));
}
}
注意:1.初始化数组,边界dp[0]=0,其他为无穷大
2.for循环方向:越少重复的方向
硬币2:
518.零钱兑换2
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
public class Coin2 {
public static int change(int amount, int[] coins) {
//输入0,[]:默认输出1
// if(coins.length==0)
// return 0;
int[] dp = new int[amount+1];
dp[0]=1;//d[0] 时为初始条件,表示0元可以用0元兑换1次,所以 d[0] = 1。
for(int i=0;i<coins.length;i++) {
for(int j=coins[i];j<=amount;j++) {
dp[j] = dp[j]+dp[j-coins[i]];
}
}
return dp[amount];
}
public static void main(String[] args) {
int[] coins1 = {1, 2, 5};
int amount1=5;
System.out.println(change(amount1,coins1));
int[] coins2 = {2};
int amount2=3;
System.out.println(change(amount2,coins2));
int[] coins3 = {10};
int amount3= 10;
System.out.println(change(amount3,coins3));
}
}
注意:1.初始化数组,边界dp[0]=1,其他为0
2.for循环方向:越少重复的方向
进制转换
String str4 = Integer.toString(i,x) ; ///10进制的数字i转换成x进制的字符串
int y4=Integer.valueOf("101",7); //7进制转换成10进制
System.out.println(y4);
Integer.valueOf("str",x); ///可以为任意进制的字符串str转换成x进制的10进制数
System.out.println("其它的可能用到的函数:");
//static int parseInt(String s, int radix) //使用第二个参数指定的基数,将字符串参数解析为有符号的整数。
int n = Integer.parseInt("776", 8) ; ///8进制转换成10进制
System.out.println(n);
///Integer.valueOf()返回一个“integer对象”和Integer.parseInt()返回一个“int值”的区别在于,返回值不同
///基本常识,其他的非10进制的数的保存,基本都是以字符串的形式
///例子:7进制到8进制的转换
String q = "6523" ; ///7进制的字符串
String b = Integer.toString(Integer.valueOf(q,7),8) ;///这样7进制就变成8进制了
/*
* 两种做法:
* 1.七进制转化为十进制,求和后在转化为七进制
* 2.两个七进制数逐一相加,有进位进一
*/
import java.util.Arrays;
public class st {
public static void main(String[] args) {
System.out.println(sumSeven(361,512));
System.out.println(sumSeven(15,12));
System.out.println(jinzhi(361,512));
System.out.println(jinzhi(15,12));
}
public static String sumSeven(int m,int n) {
int[] a = fen(m);
int[] b= fen(n);
int i=0;
int[] sum = new int[10];
int flag =0;
while(i<a.length || i<b.length) {
sum[i] = a[i]+b[i]+flag;
flag =0;
if(sum[i]>=7) {
sum[i] =sum[i]-7;
flag =1;
}
i++;
}
if(flag==1) {
sum[i] =1;
i++;
}
sum = Arrays.copyOfRange(sum, 0, i);// 截断数组
String a1 ="";
for(int i1=sum.length-1;i1>=0;i1--) {
a1 += sum[i1];
}
return a1;
}
public static int[] fen(int m) {
int[] m1 = new int[10];//假设整数有这么多位数字
int i = 0;
if (m > 0) { // 正整数
while (m > 0) {
m1[i] = m % 10;
m = m / 10;
i++;
}
}
int[] n = Arrays.copyOfRange(m1, 0, i);// 截断数组
return n;
}
/*
* 进制转换
*/
public static String jinzhi(int m,int n) {
//str = String.valueOf(int):int转string
int a =Integer.valueOf(String.valueOf(m),7);//7进制转10进制
int b =Integer.valueOf(String.valueOf(n),7);
int c =a+b;
String d = Integer.toString(c, 7);//10进制转7进制
return d;
}
}
字符串
面试的时候让手写代码
以上代码有几个错误:
1.定义的存储数组a 应该是字符串类型,不应该是int类型
2.for循环里面:i<a.length()是错误的,因为for循环里面还有i+1
3.大写字母的ASCII码比小写字母小32,不是+32
4.直接 a[i] =s[i+1]-32;i++;会导致此时的a[i]为空
5.返回值应该是字符串类型,因为自己不会写返回数组类型
总结:还是要快速大量的刷题;字符串掌握不好
自己私下敲了一下代码,用了 两种方法,一种侧重于引入数组,另一种侧重于字符串本身的操作。
引入数组:
public class XiahuaxianDaxie {
public static String transform(String s) {
String [] a = new String[s.length()];
int l=0;
for(int i=0;i<s.length()-1;i++) {
if(s.charAt(i) =='_') {
a[i] ="";//char没有'';
i++;
a[i] =String.valueOf((char)(s.charAt(i)-32));
}else
{
a[i] =String.valueOf(s.charAt(i));
}
l=i;
}
a[l+1] =String.valueOf(s.charAt(s.length()-1));
StringBuilder str = new StringBuilder();
for (String s1 : a) {
str.append(s1);
}
return str.toString(); // 012345
}
public static void main(String[] args) {
System.out.println(transform("my_str_name"));
System.out.println(transform("my_str_my_list_item"));
System.out.println(transform("my_str my_list_item"));
System.out.println(transform("usenr_name_uu_u_"));
}
}
其中,字符串数组转字符串的方法有下面两种:
引入StringBuilder/StringBuffer
StringBuilder str = new StringBuilder();
for (String s1 : a) {
str.append(s1);
}
return str.toString();
直接字符串操作:
String str1 =" ";
for (String s1 : a) {
str1 += s1;
}
return str1;
只有String类型的有"",char没有空’’.
字符串操作:
public class XiahuaxianDaxie2 {
//首字母大写
public static String captureName(String name) {
name = name.substring(0, 1).toUpperCase() + name.substring(1);
return name;
}
//将字符串name 转化为首字母大写。但是这种效率并不高,我之前看过一个牛人的写的方法核心代码,是这样的
//首字母大写
public static String captureName1(String name) {
char[] cs=name.toCharArray();
cs[0]-=32;
return String.valueOf(cs);
}
public static String transform(String s) {
String newString ="";
if(s.indexOf("_")!=-1 && s.indexOf("_") !=s.length()-1) {
while(s.indexOf("_")!=-1 && s.indexOf("_") !=s.length()-1) {
int first = s.indexOf("_");
newString = newString + s.substring(0, first);
s =captureName(s.substring(first+1));
}
newString += s;
return newString;
}
else {
return s;
}
}
public static void main(String[] args) {
System.out.println(transform("my_str_name"));
System.out.println(transform("my_str_my_list_item"));
System.out.println(transform("my_str my_list_item"));
System.out.println(transform("usenr_name_uu_u_"));
}
}
其中涉及字符串的操作在java基础知识讲解。
单例模式
深度优先搜索和广度优先搜索
求二叉树的深度或者广度
二叉树的层序遍历(bfs)
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class levelOrder {
public static List<List<Integer>> level(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root==null)
return res;
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.add(root);
while(!queue.isEmpty()) {
int count = queue.size();
List<Integer> list = new ArrayList<Integer>();
while(count>0) {
// System.out.println("count");
// System.out.println(count);
TreeNode node = queue.poll();
list.add(node.val);
// System.out.println(node.val);
if(node.left != null)
queue.add(node.left);
if(node.right != null)
queue.add(node.right);
count--;
// System.out.println(count);
}
res.add(list);
}
return res;
}
public static void main(String[] args) {
TreeNode aBiTree = new TreeNode (1);
aBiTree.left=new TreeNode(2);
aBiTree.right=new TreeNode(3);
aBiTree.left.right=new TreeNode(4);
System.out.println(level(aBiTree));
}
}
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
LinkedHashMap实现LRU
//整型
import java.util.LinkedHashMap;
import java.util.Map;
public class LRU {
LinkedHashMap<Integer, Integer> hashMap;
public LRU(int capacity) {
hashMap = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > capacity;
}
};
}
public int get(int key) {
return hashMap.getOrDefault(key, -1);
}
public void put(int key, int value) {
hashMap.put(key, value);
}
public void print() {
for (Map.Entry entry : hashMap.entrySet()) {
System.out.print(entry.getValue() + "--");
}
System.out.println();
}
public static void main(String[] args) {
LRU cache = new LRU( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.print();
System.out.println(cache.get(1)); // 返回 1
cache.print();
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.print();
System.out.println(cache.get(2)); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
}
}
//泛型
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCache<K,V> {
LinkedHashMap <K,V> hashmap;
public LRUCache(int capacity) {
hashmap = new LinkedHashMap <K,V>(capacity, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry<K,V> eldest){
return size()> capacity;
}
};
}
public V get(K key) {
return hashmap.get(key);
}
public void put(K key,V value) {
hashmap.put(key,value);
}
public void print() {
for (Map.Entry<K, V> entry : hashmap.entrySet()) {
System.out.print(entry.getValue() + "--");
}
System.out.println();
}
public static void main(String[] args) {
LRUCache<String, Integer> LRUMap = new LRUCache<>(3);
LRUMap.put("key1", 1);
LRUMap.put("key2", 2);
LRUMap.put("key3", 3);
LRUMap.print();
System.out.println();
LRUMap.put("key4", 4);
LRUMap.print();
System.out.println(LRUMap.get("key4"));
System.out.println(LRUMap.get("key5"));
}
}
微信拼手气红包
二倍均值法:
结论:剩余红包金额为M,剩余人数为N,那么有如下公式:
每次抢到的金额 = 随机区间 (0, M / N X 2)
这个公式,保证了每次随机金额的平均值是相等的,不会因为抢红包的先后顺序而造成不公平。
举例子:
假设有10个人,红包总额100元。
100/10X2 = 20, 所以第一个人的随机范围是(0,20 ),平均可以抢到10元。
假设第一个人随机到10元,那么剩余金额是100-10 = 90 元。
90/9X2 = 20, 所以第二个人的随机范围同样是(0,20 ),平均可以抢到10元。
…保证每次机会均等。
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
//@param totalAmount 总金额(以分为单位)
//二倍均值法
public class hongbao {
public static List<Integer> divideRedPackage(Integer totalAmount, Integer totalPeopleNum){
List<Integer> amountList = new ArrayList<Integer>();
Integer restAmount = totalAmount;
Integer restPeopleNum = totalPeopleNum;
Random random = new Random();
for(int i=0;i<totalPeopleNum-1;i++) {
//随机范围:[1,剩余人均金额的2倍-1]分:保证平均值为剩余人均金额
int amount =random.nextInt(restAmount/restPeopleNum*2-2)+1;
restAmount -= amount;
restPeopleNum --;
amountList.add(amount);
}
amountList.add(restAmount);//最后一个人剩余的钱
return amountList;
}
public static void main(String[] args) {
List<Integer> amountList = divideRedPackage(1000,10);
//增强for循环 foreach
for(Integer amount:amountList) {
System.out.println("抢到金额:"+new BigDecimal(amount).divide(new BigDecimal(100)));
//System.out.println("抢到金额:"+amount/100);//结果取整 没有小数点
}
}
}
线段切分法
二倍均值法缺点:除了最后一次,任何一次的金额都小于人均金额的两倍,并不是任意的均等。
如何确定每一条子线段的长度呢?由“切割点”来决定。当N个人一起抢红包的时候,就需要确定N-1个切割点。
因此,当N个人一起抢总金额为M的红包时,我们需要做N-1次随机运算,以此确定N-1个切割点。随机的范围区间是(1, M)。
当所有切割点确定以后,子线段的长度也随之确定。这样每个人来抢红包的时候,只需要顺次领取与子线段长度等价的红包金额即可。
这就是线段切割法的思路。在这里需要注意以下两点:
1.当随机切割点出现重复,如何处理。
2.如何尽可能降低时间复杂度和空间复杂度。
import java.math.BigDecimal;
import java.util.*;
//这个方法也更方便抑制红包金额MAX情况,如果金额index-start>MAX,就直接把红包设为最大值MAX,
//然后随机点重置为start+MAX,保证所有红包金额相加等于总金额。
public class hongbaoxianduanqiege {
public static List<Integer> divideRedPackage(int allMoney, int peopleCount,int MAX) {
//人数比钱数多则直接返回错误
if(peopleCount<1||allMoney<peopleCount){
System.out.println("钱数人数设置错误!");
return null;
}
List<Integer> indexList = new ArrayList<>();
List<Integer> amountList = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < peopleCount - 1; i++) {
int index;
do{
index = random.nextInt(allMoney - 2) + 1;
}while (indexList.contains(index));//解决碰撞
indexList.add(index);
}
Collections.sort(indexList);
int start = 0;
for (Integer index:indexList) {
//解决最大红包值
if(index-start>MAX){
amountList.add(MAX);
start=start+MAX;
}else{
amountList.add(index-start);
start = index;
}
}
amountList.add(allMoney-start);
return amountList;
}
public static void main(String args[]){
Scanner in=new Scanner(System.in);
int n=Integer.parseInt(in.nextLine());
int pnum=Integer.parseInt(in.nextLine());
int money=n*100;int max=n*90;
List<Integer> amountList = divideRedPackage(money, pnum,max);
if(amountList!=null){
for (Integer amount : amountList) {
System.out.println("抢到金额:" + new BigDecimal(amount).divide(new BigDecimal(100)));
}
}}
}
五亿大整数,怎么排
BItmap 大数据的应用
买卖股票
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
max( 选择 rest , 选择 sell )
解释:今天我没有持有股票,有两种可能:
要么是我昨天就没有持有,然后今天选择 rest,所以我今天还是没有持有;
要么是我昨天持有股票,但是今天我 sell 了,所以我今天没有持有股票了。
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
max( 选择 rest , 选择 buy )
解释:今天我持有着股票,有两种可能:
要么我昨天就持有着股票,然后今天选择 rest,所以我今天还持有着股票;
要么我昨天本没有持有,但今天我选择 buy,所以今天我就持有股票了。
dp[-1][k][0] = 0
解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0 。
dp[-1][k][1] = -infinity
解释:还没开始的时候,是不可能持有股票的,用负无穷表示这种不可能。
dp[i][0][0] = 0
解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0 。
dp[i][0][1] = -infinity
解释:不允许交易的情况下,是不可能持有股票的,用负无穷表示这种不可能。
状态转移方程:
base case:
dp[-1][k][0] = dp[i][0][0] = 0
dp[-1][k][1] = dp[i][0][1] = -infinity
状态转移方程:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])