判断是否有环
问题:判断一个链表是否有环? 解题方法: 假设链表的节点数量是n,则该算法的时间复杂度为O(n),除两个指针外没有使用任何额外的存储空间,所以空间复杂度是O(1)。 问题扩展
环长度? 入环长度? 代码
private static class Node {
int data;
Node next;
Node ( int data)
{ this . data = data; }
}
public static boolean isCycle ( Node head)
{
Node p1 = head;
Node p2 = head;
while ( p2 != null && p2. next != null)
{
p1 = p1. next;
p2 = p2. next. next;
if ( p1 == p2)
{ return true ; }
}
return false ;
}
public static int cycleLength ( Node head)
{
Node p1 = head;
Node p2 = head;
int cycleIndex = 0 ;
int cyclelength = 0 ;
while ( true )
{
p1 = p1. next;
p2 = p2. next. next;
cyclelength++ ;
if ( p1 == p2)
{
if ( cycleIndex == 1 )
{
return cyclelength - 1 ;
}
cycleIndex++ ;
cyclelength = 0 ;
}
}
}
public static Node inCycleIndex ( Node head)
{
Node p1 = head;
Node p2 = head;
int cycleIndex = 0 ;
int ret = 0 ;
while ( true )
{
p1 = p1. next;
if ( cycleIndex == 1 )
{
p2 = p2. next;
}
else {
p2 = p2. next. next;
}
ret++ ;
if ( p1 == p2)
{
if ( cycleIndex == 1 )
{
return p1;
}
p2 = head;
cycleIndex++ ;
ret = 0 ;
}
}
}
public static void main ( String[ ] args) throws Exception
{
Node node1 = new Node ( 5 ) ;
Node node2 = new Node ( 3 ) ;
Node node3 = new Node ( 7 ) ;
Node node4 = new Node ( 2 ) ;
Node node5 = new Node ( 6 ) ;
node1. next = node2;
node2. next = node3;
node3. next = node4;
node4. next = node5;
node5. next = node2;
System. out. println ( isCycle ( node1) ) ;
}
最小栈的实现
问题: 解决步骤: 这个解法中进栈、出栈、取最小值的时间复杂度都是O(1),最坏情况空间复杂度是O(n)。 代码
private Stack< Integer> mainStack = new Stack < Integer> ( ) ;
private Stack< Integer> minStack = new Stack < Integer> ( ) ;
public void push ( int element)
{
mainStack. push ( element) ;
if ( minStack. empty ( ) || element < minStack. peak ( ) )
{
minStack. push ( element) ;
}
}
public Integer pop ( )
{
if ( mainStack. peek ( ) . equals ( minStack. peek ( ) ) )
{
minStack. pop ( ) ;
}
return mainStack. pop ( ) ;
}
punblic int getMin ( ) throws Exception{
if ( mainStack. empty ( ) )
{
throws new Exception ( "stack is empty" ) ;
}
return minStack. peek ( ) ;
}
public static void main ( String[ ] args) throws Exception{
MinStack stack = new MinStack ( ) ;
stack. push ( 4 ) ;
stack. push ( 9 ) ;
stack. push ( 7 ) ;
stack. push ( 3 ) ;
stack. push ( 8 ) ;
stack. push ( 5 ) ;
System. out. println ( stack. getMin ( ) ) ;
stack. pop ( ) ;
stack. pop ( ) ;
stack. pop ( ) ;
System. out. println ( stack. getMin ( ) ) ;
}
求两个数的最大公约数
辗转相除法。 首先计算出a除以b的余数c,如果c为0则最大公约数就是b,如果不为0,则把问题转化成为b和c的最大公约数;然后计算出b除以c的余数d,把问题转化为c和d的最大公约数,依次类推。如果余数为1,则不用往下进行,结果就是1. 改进版的更相减损法 代码
public static int getGreatestCommonDivisor ( int a , int b)
{
int c = a % b;
if ( c == 0 )
{
return b;
}
else if ( c == 1 )
{
return 1 ;
}
else
{
return getGreatestCommonDivisor ( b , c) ;
}
}
public static int gcd ( int a, int b)
{
if ( a == b)
{
return a;
}
if ( ( a & 1 ) == 0 && ( b & 1 ) == 0 )
{
return gcd ( a >> 1 , b >> 1 ) << 1 ;
}
else if ( ( a & 1 ) == 0 && ( b & 1 ) == 1 )
{
return gcd ( a >> 1 , b) ;
}
else if ( ( a & 1 ) == 1 && ( b & 1 ) == 0 )
{
return gcd ( a, b >> 1 ) ;
}
else
{
int big = a > b ? a : b;
int small = a < b ? a : b;
return gcd ( smal, big - small) ;
}
}
public static void main ( String[ ] args)
{
System. out. println ( getGreatestCommonDivisor ( 25 , 5 ) ) ;
}
总结:
暴力枚举法:时间复杂度:O(min(a, b)). 辗转相除法:时间复杂度:O(log(max(a, b))),缺点是取模运算性能较差。 更相减损法:避免了取模运算,但是算法性能不稳定,最坏的时间复杂度为O(max(a, b)); 更相减损法和移位相结合:不但避免了取模运算,而且算法稳定,时间复杂度为:O(log(max(a, b)))
如何判断一个数是否为2的整数次幂
解题方法发:如果一个数是为2的整数次幂,则他转化为二进制为1000…,将该值减1,再转化为二进制,为111…。将这两个二进制取与,得到的为0. 代码
public static boolean isPowerOf2 ( int num)
{
return ( num & num - 1 ) == 0 ;
}
无序数组排序后的最大相邻差
问题:无序数组排序后的最大相邻差 解题方法: 代码
public static int getMaxSortedDistance ( int [ ] array)
{
int max = array[ 0 ] ;
int min = array[ 0 ] ;
for ( int i = 1 ; i < array. length; i++ )
{
if ( array[ i] > max)
{
max = array[ i] ;
}
if ( array[ i] < min)
{
min = array[ i] ;
}
}
int d = max - min;
if ( d == 0 )
{
return 0 ;
}
int bucketNum = array. length;
Bucket[ ] buckets = new Bucket [ bucketNum] ;
for ( int i = 1 ; i < array. length; i++ )
{
buckets[ i] = new Bucket ( ) ;
}
for ( int i = 1 ; i < array. length; i++ )
{
int index = ( array[ i] - min) * ( bucketNum - 1 ) / d;
if ( buckets[ index] . min == null || buckets[ index] . min > array[ i] )
{
buckets[ index] . min = array[ i] ;
}
if ( buckets[ index] . max == null || buckets[ index] . max < array[ i] )
{
buckets[ index] . max = array[ i] ;
}
}
int leftMax = buckets[ 0 ] . max;
int maxDistance = 0 ;
for ( int i = 1 ; i < buckets. length; i++ )
{
if ( buckets[ i] . min == null)
{
continue ;
}
if ( buckets[ i] . min - leftMax > maxDistance)
{
maxDistance = buckets[ i] . min - leftMax;
}
leftMax = buckets[ i] . max;
}
return maxDistance;
}
private void class Bucket
{
Integer min;
Integer max;
}
public static void main ( String[ ] args)
{
int [ ] array = new int [ ] { 2 , 6 , 3 , 4 , 5 , 10 , 9 } ;
System. out. println ( getMaxSortedDistance ( array) ) ;
}
如何用栈时间队列
解题方法:队列这里只考虑两个操作分别是入队和出队。入队:直接将一个新元素压入到栈A当中,出队:让栈A中所有的元素按照顺序出栈,再按照出栈顺序压入栈B,然后从栈B中出栈就好。 时间复杂度:
入队的时间复杂度是O(1); 出队的时间复杂度:这里涉及到一个新的概念,叫做均摊时间复杂度,需要元素迁移的出队操作只有少数情况,并且不可能连续出现,其后的大多数出队操作都不需要元素迁移,所以把时间均摊到每一次出队操作上面,其时间复杂度是O(1)。 代码
private Stack< Integer> stackA = new Stack < Integer> ( ) ;
private Stack< Integer> stackB = new Stack < Integer> ( ) ;
public void enQueue ( int element)
{
stackA. push ( element) ;
}
public Integer deQueue ( ) {
if ( stackB. isEmpty ( ) )
{
if ( stackA. isEmpty ( ) )
{
return null
}
transfer ( ) ;
}
return stackB. pop ( ) ;
}
private void transfer ( )
{
while ( ! stackA. isEmpty ( ) ) {
stackB. push ( stackA. pop ( ) ) ;
}
}
public static void main ( String[ ] args) throws Exception {
StackQueue stackQueue = new StackQueue ( ) ;
stackQueue. enQueue ( 1 ) ;
stackQueue. enQueue ( 2 ) ;
stackQueue. enQueue ( 3 ) ;
System. out. println ( stackQueue. deQueue ( ) ) ;
System. out. println ( stackQueue. deQueue ( ) ) ;
stackQueue. enQueue ( 4 ) ;
System. out. println ( stackQueue. deQueue ( ) ) ;
System. out. println ( stackQueue. deQueue ( ) ) ;
}
寻找一个整数所有数组全排列的下一个数
解题方法:字典序算法 时间复杂度:该算法3个步骤每一步的时间复杂度都是O(n),所以整个时间复杂度也是O(n). 代码
public static int [ ] findNearestNumber ( int [ ] numbers)
{
int index = findTransferPoint ( numbers) ;
if ( index == 0 )
{
return null;
}
int [ ] numbersCopy = Arrays. copyOf ( numbers, numbers. length) ;
exchangeHead ( numbersCopy, index) ;
reverse ( numbersCopy, index) ;
return numbersCopy;
}
private static int findTransferPoint ( int [ ] numbers)
{
for ( int i = numbers. length - 1 ; i > 0 ; i-- )
{
if ( numbers[ i] > numers[ i - 1 ] )
{
return i;
}
}
return 0 ;
}
private static void exchangedHead ( int [ ] numbers, int index)
{
int head = numbers[ index - 1 ] ;
for ( int i = numbers. length - 1 ; i > 0 ; i-- )
{
if ( head < numbers[ i] )
{
numbers[ index - 1 ] = numbers[ i] ;
numbers[ i] = head;
break ;
}
}
}
private static void reverse ( int [ ] num, int index)
{
for ( int i = index, j = num. length - 1 ; i < j; i++ , j-- )
{
int temp = num[ i] ;
num[ i] = num[ j] ;
num[ j] = temp;
}
}
pubic static void main ( String[ ] args)
{
int [ ] numbers = { 1 , 2 , 3 , 4 , 5 } ;
for ( int i = 0 ; i < 10 ; i++ )
{
numbers = findNearstNumber ( numbers) ;
outputNumbers ( numbers) ;
}
}
private static void outputNumbers ( int [ ] numbers) {
for ( int i : numbers)
{
System. out. println ( i) ;
}
}
怎么样寻找删除K个数字后的最小值
问题描述: 解决方法:此时,无论删除哪一个数字,最后的结果都是从9位整数变为8位整数。既然同样是8位整数,显然应该优先把高位的数降低,这样对新整数的值影响最大。如果把高位的是最降低呢?把原整数的所有数字从左到右进行比较,如果发现某一位数字大于它右边的数字,那么在删除该数字后,必然会使得该数位的值降低,因为右边比他小的数字顶替他的位置。 时间复杂度:上面的方法只对所有数字遍历了一次,遍历的时间复杂度是O(n),把栈转化为字符串的时间复杂度也是O(n),所以最终的时间复杂度是O(n). 代码
public static String removeKDigists ( String num, int k )
{
int newLength = num. length ( ) - k;
char [ ] stack = new char [ num. length ( ) ] ;
int top = 0 ;
for ( int i = 0 ; i < num. length ( ) ; i++ )
{
char c = num. charAt ( i) ;
while ( i > 0 && stack[ top - 1 ] > c && k > 0 )
{
top-- ;
k-- ;
}
stack[ top++ ] = c;
}
int offset = 0 ;
while ( offset < newLength && stack[ offset] == '0' )
{
offset++ ;
}
return offset == newLength ? "0" : new String ( stack, offset, newLength - offset) ;
}
public static void main ( String[ ] args) {
System. out. println ( removeKDigits ( "1593212" , 3 ) ) ;
System. out. println ( removeKDigits ( "30200" , 1 ) ) ;
System. out. println ( removeKDigits ( "10" , 2 ) ) ;
System. out. println ( removeKDigits ( "541270936" , 3 ) ) ;
}
挖金矿
问题描述: 解题方法:
这是动态规划,首先对于问题中的金矿来说,每个金矿都存在这挖和不挖两种选择,如果最后一个金矿不挖,则问题变成10个人在前面4个金矿中选择,如果挖,则最后一个金矿消耗3个工人,问题简化成7个工人在前面4个金矿中选择。依次类推,所以: 吧金矿数量设为n,工人数量设为w,金矿的含金量设为数组g[],金矿所需开采人数设为p[],设F(n, w)为n个金矿,w个工人时的最优收益函数,那么状态转移方程是: F(n, w) = 0 (n = 0 或者 w = 0) 问题边界:金矿数为0或者工人数为0的情况。
F(n, w) = max(F(n - 1, w), F(n - 1, w - p[n - 1]) + g[n - 1]) (n > 0, w >= p[n - 1])
表格解决思路: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-diDdRgUD-1611191075515)(/图片资源/挖金矿表格.png )] F(1,1) = F(1-1, 1) = F(0, 1) = 0
F(1,2) = F(1-1, 2) = F(0, 2) = 0
F(1,3) = F(1-1, 3) = F(0, 3) = 0
F(1,4) = F(1-1, 4) = F(0, 4) = 0
F(1,5) = max(F(1-1, 5), F(1-1, 5-5)) = max(F(0, 5), F(0, 0) + 400) = 400
表格法的优化:一般的思路是new一个二维数组,用来装表格,但是这样就开辟了nw的空间,优化就在这上面。在表格的第一行之外,每一行的结果都是由上一行数据推到出来的,以5个金矿9个工人为例,4个金矿、9个工人的最优结果是由它的两个最优子结构,也就是3个金矿、5个工人和3个金矿、9个工人的结果推到出来的,这两个最优子结构都是位于它的上一行。所以在程序中并不需要保存整个表格,无论金矿多少座,我们只保存一行的数据即可,在计算下一行时,要从右向左统计 。 复杂度:
代码
public static int getBestGoldMiningV1 ( int w, int [ ] p, int [ ] g)
{
int n = g. length;
int ret = ne int [ w + 1 ] ;
for ( int i= 1 ; i <= n; i++ )
{
for ( int j= w; j > 0 ; j-- )
{
if ( j >= p[ i - 1 ] )
{
ret[ j] = Math. max ( ret[ j] , ret[ j - p[ i - 1 ] ] + g[ i - 1 ] ) ;
}
}
}
return ret[ w] ;
}