2020年4月21日

今日算法題 1248 優美子數組

給你一個整數數組 nums 和一個整數 k

如果某個 連續 子數組中恰好有 k 個奇數數字,我們就認爲這個子數組是「優美子數組」。

請返回這個數組中「優美子數組」的數目。

示例 1:

輸入:nums = [1,1,2,1,1], k = 3
輸出:2
解釋:包含 3 個奇數的子數組是 [1,1,2,1] 和 [1,2,1,1] 。

示例 2:

輸入:nums = [2,4,6], k = 1
輸出:0
解釋:數列中不包含任何奇數,所以不存在優美子數組。

示例 3:

輸入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
輸出:16

提示:

  • 1 <= nums.length <= 50000
  • 1 <= nums[i] <= 10^5
  • 1 <= k <= nums.length
public class NumberOfSubarrays {
	//第一種暴力破解 自己想出來的,但是提交時用例超時
	public static int numberOfSubarrays(int[] nums, int k) {
		int result = 0 ;
		int index = 0 ;
		int tempIndex = index;
		int flag = 0;
		while (index < nums.length  ){
			if(nums[tempIndex]%2 == 1){
				flag++;
			}
			if(flag == k){
				result++;
			}else if(flag > k){
				index++;
				flag = 0;
				tempIndex = index;
				continue;
			}
			tempIndex++;
			if(tempIndex >= nums.length){
				index++;
				flag = 0;
				tempIndex = index;
			}
		}
		return result;
	}
	//第二種爲使用數據計算方式
	//TODO (odd[i]−odd[i−1])∗(odd[i+k]−odd[i+k−1])
	public static int numberOfSubarrays2(int[] nums, int k) {
		int length = nums.length;
		int[] odd = new int[length+2];
		int ans = 0;
		int cnt = 0;
		for (int i = 0; i < length; i++) {
			if(nums[i]%2 == 1){
				odd[++cnt] = i ;
			}
		}
		odd[0] = -1;
		odd[++cnt] = length;
		for (int i = 1; i +k <= cnt ; i++) {
			ans += (odd[i]-odd[i-1])*(odd[i+k] - odd[i+k-1]);
		}
		return ans;
	}
	//45627,50891,94884,11286,35337,46414,62029,20247,72789,89158,54203,79628,25920,16832,47469,80909
	public static void main(String[] args) {
		int i = numberOfSubarrays2(new int[]{1,1,2,1,1}, 3);
		System.out.println(i);
	}
}

java面試題

  1. ThreadLocal 是什麼?有哪些使用場景?
    線程局部變量是侷限於線程內部的變量,屬於線程自身所有,不在多個線程間共享。Java提供ThreadLocal類來支持線程局部變量,是一種實現線程安全的方式。但是在管理環境下(如 web 服務器)使用線程局部變量的時候要特別小心,在這種情況下,工作線程的生命週期比任何應用變量的生命週期都要長。任何線程局部變量一旦在工作完成後沒有釋放,Java 應用就存在內存泄露的風險。
    52.說一下 synchronized 底層實現原理?
    synchronized可以保證方法或者代碼塊在運行時,同一時刻只有一個方法可以進入到臨界區,同時它還可以保證共享變量的內存可見性。
    Java中每一個對象都可以作爲鎖,這是synchronized實現同步的基礎:
  • 普通同步方法,鎖是當前實例對象
  • 靜態同步方法,鎖是當前類的class對象
  • 同步方法塊,鎖是括號裏面的對象
  1. synchronized 和 volatile 的區別是什麼?
  • volatile本質是在告訴jvm當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取;synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。
  • volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的
  • volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性。
  • volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。
  • volatile標記的變量不會被編譯器優化;synchronized標記的變量可以被編譯器優化。
  1. synchronized 和 Lock 有什麼區別?
  • 首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;
  • synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖;
  • synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖;
  • 用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了;
  • synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可);
  • Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。
  1. synchronized 和 ReentrantLock 區別是什麼?
    synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別。既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現在幾點上:
  • ReentrantLock可以對獲取鎖的等待時間進行設置,這樣就避免了死鎖
  • ReentrantLock可以獲取各種鎖的信息
  • ReentrantLock可以靈活地實現多路通知
    另外,二者的鎖機制其實也是不一樣的:ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操作的應該是對象頭中mark word。
  1. 說一下 atomic 的原理?
    Atomic包中的類基本的特性就是在多線程環境下,當有多個線程同時對單個(包括基本類型及引用類型)變量進行操作時,具有排他性,即當多個線程同時對該變量的值進行更新時,僅有一個線程能成功,而未成功的線程可以向自旋鎖一樣,繼續嘗試,一直等到執行成功。
    Atomic系列的類中的核心方法都會調用unsafe類中的幾個本地方法。我們需要先知道一個東西就是Unsafe類,全名爲:sun.misc.Unsafe,這個類包含了大量的對C代碼的操作,包括很多直接內存分配以及原子操作的調用,而它之所以標記爲非安全的,是告訴你這個裏面大量的方法調用都會存在安全隱患,需要小心使用,否則會導致嚴重的後果,例如在通過unsafe分配內存的時候,如果自己指定某些區域可能會導致一些類似C++一樣的指針越界到其他進程的問題。
  2. 什麼是反射?
    反射主要是指程序可以訪問、檢測和修改它本身狀態或行爲的一種能力
    Java反射:
    在Java運行時環境中,對於任意一個類,能否知道這個類有哪些屬性和方法?對於任意一個對象,能否調用它的任意一個方法
    Java反射機制主要提供了以下功能:
  • 在運行時判斷任意一個對象所屬的類。
  • 在運行時構造任意一個類的對象。
  • 在運行時判斷任意一個類所具有的成員變量和方法。
  • 在運行時調用任意一個對象的方法。
  1. 什麼是 java 序列化?什麼情況下需要序列化?
    簡單說就是爲了保存在內存中的各種對象的狀態(也就是實例變量,不是方法),並且可以把保存的對象狀態再讀出來。雖然你可以用你自己的各種各樣的方法來保存object states,但是Java給你提供一種應該比你自己好的保存對象狀態的機制,那就是序列化。
    什麼情況下需要序列化:
    a)當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候;
    b)當你想用套接字在網絡上傳送對象的時候;
    c)當你想通過RMI傳輸對象的時候;
  2. 動態代理是什麼?有哪些應用?
    動態代理:
    當想要給實現了某個接口的類中的方法,加一些額外的處理。比如說加日誌,加事務等。可以給這個類創建一個代理,故名思議就是創建一個新的類,這個類不僅包含原來類方法的功能,而且還在原來的基礎上添加了額外處理的新類。這個代理類並不是定義好的,是動態生成的。具有解耦意義,靈活,擴展性強。
    動態代理的應用:
  • Spring的AOP
  • 加事務
  • 加權限
  • 加日誌
  1. 怎麼實現動態代理?
  • JDK動態代理
    首先必須定義一個接口,還要有一個InvocationHandler(將實現接口的類的對象傳遞給它)處理類。再有一個工具類Proxy(習慣性將其稱爲代理類,因爲調用他的newInstance()可以產生代理對象,其實他只是一個產生代理對象的工具類)。利用到InvocationHandler,拼接代理類源碼,將其編譯生成代理類的二進制碼,利用加載器加載,並將其實例化產生代理對象,最後返回。
public interface People {
    public void sayHello();
}
public class Chinese implements People {

    @Override
    public void sayHello() {
        System.out.println("Chinese say hello.");
    }
}

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PeopleInvocationHandler implements InvocationHandler{
    
    private Object peolple;
    
    Intermediary(Object people){
        this.people = people;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {

        Object invoke = method.invoke(people, args);
        System.out.println("-------- end ---------");
        return invoke;
    }
}

import java.lang.reflect.Proxy;
public class Test {
    public static void main(String[] args) {
        People chinese = new People();
        PeopleInvocationHandler invocationHandler = new PeopleInvocationHandler(chinese);
        People proxy = (People) Proxy.newProxyInstance(chinese.getClass().getClassLoader(), chinese.getClass().getInterfaces(), invocationHandler);
        proxy.sayHello();
    }
}
  • CGLIB 代理
public class Chinese {
    public void sayHello(){
        System.out.println("Chinese say hello");
    }
}

import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class ChinesePoxy implements MethodInterceptor {
    @Override
    public Object intercept(Object object, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {
        Object intercept = methodProxy.invokeSuper(object, args);
     System.out.println("-------- end ---------");
    return intercept; 
  } 
}

import net.sf.cglib.proxy.Enhancer;
public class Test {
    public static void main(String[] args) {
        ChineseProxy chineseProxy = new ChineseProxy();
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(Chinese.class);
        enhancer.setCallback(chineseProxy);
        Chinese proxy = (Chinese) enhancer.create();
        proxy.sayHello();
    }
}
  1. 爲什麼要使用克隆?
    想對一個對象進行處理,又想保留原有的數據進行接下來的操作,就需要克隆了,Java語言中克隆針對的是類的實例。
  2. 如何實現對象克隆?
    1). 實現Cloneable接口並重寫Object類中的clone()方法;
    2). 實現Serializable接口,通過對象的序列化和反序列化實現克隆,可以實現真正的深度克隆,代碼如下:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class MyUtil {
 
    private MyUtil() {
        throw new AssertionError();
    }
 
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj) throws Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bout);
        oos.writeObject(obj);
 
        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bin);
        return (T) ois.readObject();
 
        // 說明:調用ByteArrayInputStream或ByteArrayOutputStream對象的close方法沒有任何意義
        // 這兩個基於內存的流只要垃圾回收器清理對象就能夠釋放資源,這一點不同於對外部資源(如文件流)的釋放
    }
}

下面是測試代碼:

import java.io.Serializable;
/**
 * 人類
 * @author nnngu
 */
class Person implements Serializable {
    private static final long serialVersionUID = -9102017020286042305L;
 
    private String name;    // 姓名
    private int age;        // 年齡
    private Car car;        // 座駕
 
    public Person(String name, int age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Car getCar() {
        return car;
    }
    public void setCar(Car car) {
        this.car = car;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
    }
}
/**
 * 小汽車類
 * @author nnngu
 *
 */
class Car implements Serializable {
    private static final long serialVersionUID = -5713945027627603702L;
    private String brand;       // 品牌
    private int maxSpeed;       // 最高時速
    public Car(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public int getMaxSpeed() {
        return maxSpeed;
    }
    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }
    @Override
    public String toString() {
        return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
    }
}
class CloneTest {
    public static void main(String[] args) {
        try {
            Person p1 = new Person("郭靖", 33, new Car("Benz", 300));
            Person p2 = MyUtil.clone(p1);   // 深度克隆
            p2.getCar().setBrand("BYD");
            // 修改克隆的Person對象p2關聯的汽車對象的品牌屬性
            // 原來的Person對象p1關聯的汽車不會受到任何影響
            // 因爲在克隆Person對象時其關聯的汽車對象也被克隆了
            System.out.println(p1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意:基於序列化和反序列化實現的克隆不僅僅是深度克隆,更重要的是通過泛型限定,可以檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用Object類的clone方法克隆對象。讓問題在編譯的時候暴露出來總是好過把問題留到運行時。
63. 深拷貝和淺拷貝區別是什麼?
淺拷貝只是複製了對象的引用地址,兩個對象指向同一個內存地址,所以修改其中任意的值,另一個值都會隨之變化,這就是淺拷貝(例:assign())
深拷貝是將對象及值複製過來,兩個對象修改其中任意的值另一個值不會改變,這就是深拷貝(例:JSON.parse()和JSON.stringify(),但是此方法無法複製函數類型)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章